mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
|
||||
import com.kunzisoft.keepass.search.SearchDbHelper;
|
||||
import com.kunzisoft.keepass.database.search.SearchDbHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,8 +72,8 @@ public class DeleteEntry extends AndroidTestCase {
|
||||
|
||||
// Verify the entries were removed from the search index
|
||||
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
|
||||
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME);
|
||||
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME);
|
||||
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
|
||||
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
|
||||
|
||||
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
||||
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.tests.database;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV4;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class EntryV4 extends TestCase {
|
||||
|
||||
public void testBackup() {
|
||||
@@ -46,7 +46,7 @@ public class EntryV4 extends TestCase {
|
||||
entry.createBackup(db);
|
||||
|
||||
PwEntryV4 backup = entry.getHistory().get(0);
|
||||
entry.endToManageFieldReferences();
|
||||
entry.stopToManageFieldReferences();
|
||||
assertEquals("Title2", backup.getTitle());
|
||||
assertEquals("User2", backup.getUsername());
|
||||
}
|
||||
|
||||
@@ -104,11 +104,19 @@
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.GroupActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:launchMode="singleTop">
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.kunzisoft.keepass.search.SearchResults"
|
||||
android:exported="false"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.EntryActivity"
|
||||
@@ -118,17 +126,6 @@
|
||||
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.search.SearchResultsActivity"
|
||||
android:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
|
||||
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
|
||||
android:configChanges="orientation|keyboardHidden" />
|
||||
|
||||
@@ -225,7 +225,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
mEntry.endToManageFieldReferences();
|
||||
mEntry.stopToManageFieldReferences();
|
||||
}
|
||||
firstLaunchOfActivity = false;
|
||||
}
|
||||
@@ -369,7 +369,7 @@ public class EntryActivity extends LockingHideActivity {
|
||||
entryContentsView.assignExpiresDate(getString(R.string.never));
|
||||
}
|
||||
|
||||
mEntry.endToManageFieldReferences();
|
||||
mEntry.stopToManageFieldReferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -429,7 +429,7 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
}
|
||||
|
||||
newEntry.endToManageFieldReferences();
|
||||
newEntry.stopToManageFieldReferences();
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
@@ -484,7 +484,7 @@ public class EntryEditActivity extends LockingHideActivity
|
||||
}
|
||||
|
||||
// Don't start the field reference manager, we want to see the raw ref
|
||||
mEntry.endToManageFieldReferences();
|
||||
mEntry.stopToManageFieldReferences();
|
||||
|
||||
entryTitleView.setText(mEntry.getTitle());
|
||||
entryUserNameView.setText(mEntry.getUsername());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.SearchManager;
|
||||
@@ -29,24 +30,31 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
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.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.getkeepsafe.taptargetview.TapTarget;
|
||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
||||
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
@@ -57,6 +65,7 @@ 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.node.AddGroupRunnable;
|
||||
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
|
||||
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
|
||||
@@ -69,48 +78,64 @@ import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
|
||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
import com.kunzisoft.keepass.search.SearchResultsActivity;
|
||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||
import com.kunzisoft.keepass.password.AssignPasswordHelper;
|
||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
||||
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.utils.MenuUtil;
|
||||
import com.kunzisoft.keepass.view.AddNodeButtonView;
|
||||
|
||||
import net.cachapa.expandablelayout.ExpandableLayout;
|
||||
|
||||
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
|
||||
|
||||
public class GroupActivity extends ListNodesActivity
|
||||
public class GroupActivity extends LockingActivity
|
||||
implements GroupEditDialogFragment.EditGroupListener,
|
||||
IconPickerDialogFragment.IconPickerListener,
|
||||
NodeAdapter.NodeMenuListener,
|
||||
ListNodesFragment.OnScrollListener {
|
||||
ListNodesFragment.OnScrollListener,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
NodeAdapter.NodeClickCallback,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
private static final String TAG = GroupActivity.class.getName();
|
||||
|
||||
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||
private static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
|
||||
private static final String SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG";
|
||||
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 Toolbar toolbar;
|
||||
|
||||
private View searchTitleView;
|
||||
private ExpandableLayout toolbarPasteExpandableLayout;
|
||||
private Toolbar toolbarPaste;
|
||||
|
||||
private ImageView iconView;
|
||||
private AddNodeButtonView addNodeButtonView;
|
||||
private TextView groupNameView;
|
||||
|
||||
protected boolean addGroupEnabled = false;
|
||||
protected boolean addEntryEnabled = false;
|
||||
protected boolean isRoot = false;
|
||||
private Database database;
|
||||
|
||||
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 ListNodesFragment listNodesFragment;
|
||||
private boolean currentGroupIsASearch;
|
||||
|
||||
private PwGroup rootGroup;
|
||||
private PwGroup mCurrentGroup;
|
||||
private PwGroup oldGroupToUpdate;
|
||||
private PwNode nodeToCopy;
|
||||
private PwNode nodeToMove;
|
||||
|
||||
private boolean entrySelectionMode;
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
private SearchEntryCursorAdapter searchSuggestionAdapter;
|
||||
|
||||
// After a database creation
|
||||
public static void launch(Activity act) {
|
||||
launch(act, READ_ONLY_DEFAULT);
|
||||
@@ -176,39 +201,37 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.i(TAG, "Started creating tree");
|
||||
if ( mCurrentGroup == null ) {
|
||||
Log.w(TAG, "Group was null");
|
||||
return;
|
||||
}
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
database = App.getDB();
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! database.getLoaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct main view
|
||||
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
|
||||
|
||||
attachFragmentToContentView();
|
||||
|
||||
// Initialize views
|
||||
iconView = findViewById(R.id.icon);
|
||||
addNodeButtonView = findViewById(R.id.add_node_button);
|
||||
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
||||
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
||||
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
searchTitleView = findViewById(R.id.search_title);
|
||||
groupNameView = findViewById(R.id.group_name);
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
invalidateOptionsMenu();
|
||||
|
||||
// Get arg from intent or instance state
|
||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
|
||||
|
||||
// Retrieve elements after an orientation change
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
||||
oldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY);
|
||||
@@ -223,21 +246,124 @@ public class GroupActivity extends ListNodesActivity
|
||||
}
|
||||
}
|
||||
|
||||
addNodeButtonView.setAddGroupClickListener(v -> {
|
||||
GroupEditDialogFragment.build()
|
||||
.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
rootGroup = database.getPwDatabase().getRootGroup();
|
||||
mCurrentGroup = retrieveCurrentGroup(getIntent(), savedInstanceState);
|
||||
currentGroupIsASearch = Intent.ACTION_SEARCH.equals(getIntent().getAction());
|
||||
|
||||
Log.i(TAG, "Started creating tree");
|
||||
if ( mCurrentGroup == null ) {
|
||||
Log.w(TAG, "Group was null");
|
||||
return;
|
||||
}
|
||||
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
String fragmentTag = LIST_NODES_FRAGMENT_TAG;
|
||||
if (currentGroupIsASearch)
|
||||
fragmentTag = SEARCH_FRAGMENT_TAG;
|
||||
|
||||
// Initialize the fragment with the list
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(fragmentTag);
|
||||
if (listNodesFragment == null)
|
||||
listNodesFragment = ListNodesFragment.newInstance(mCurrentGroup, readOnly, currentGroupIsASearch);
|
||||
|
||||
// Attach fragment to content view
|
||||
getSupportFragmentManager().beginTransaction().replace(
|
||||
R.id.nodes_list_fragment_container,
|
||||
listNodesFragment,
|
||||
fragmentTag)
|
||||
.commit();
|
||||
|
||||
// Add listeners to the add buttons
|
||||
addNodeButtonView.setAddGroupClickListener(v -> GroupEditDialogFragment.build()
|
||||
.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP));
|
||||
addNodeButtonView.setAddEntryClickListener(v ->
|
||||
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
|
||||
|
||||
Log.i(TAG, "Finished creating tree");
|
||||
|
||||
if (isRoot) {
|
||||
showWarnings();
|
||||
// To init autofill
|
||||
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
|
||||
// Search suggestion
|
||||
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
|
||||
|
||||
Log.i(TAG, "Finished creating tree");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
setIntent(intent);
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
// only one instance of search in backstack
|
||||
openSearchGroup(retrieveCurrentGroup(intent, null));
|
||||
currentGroupIsASearch = true;
|
||||
} else {
|
||||
currentGroupIsASearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openSearchGroup(PwGroup group) {
|
||||
// Delete the previous search fragment
|
||||
Fragment searchFragment = getSupportFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
|
||||
if (searchFragment != null) {
|
||||
if ( getSupportFragmentManager()
|
||||
.popBackStackImmediate(SEARCH_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) )
|
||||
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||
}
|
||||
|
||||
openGroup(group, true);
|
||||
}
|
||||
|
||||
private void openChildGroup(PwGroup group) {
|
||||
openGroup(group, false);
|
||||
}
|
||||
|
||||
private void openGroup(PwGroup group, boolean isASearch) {
|
||||
// Check Timeout
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
// Open a group in a new fragment
|
||||
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly, isASearch);
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
// Different animation
|
||||
String fragmentTag;
|
||||
if (isASearch) {
|
||||
fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_out_bottom,
|
||||
R.anim.slide_in_bottom, R.anim.slide_out_top);
|
||||
fragmentTag = SEARCH_FRAGMENT_TAG;
|
||||
} else {
|
||||
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right);
|
||||
fragmentTag = LIST_NODES_FRAGMENT_TAG;
|
||||
}
|
||||
|
||||
fragmentTransaction.replace(R.id.nodes_list_fragment_container,
|
||||
newListNodeFragment,
|
||||
fragmentTag);
|
||||
fragmentTransaction.addToBackStack(fragmentTag);
|
||||
fragmentTransaction.commit();
|
||||
|
||||
listNodesFragment = newListNodeFragment;
|
||||
mCurrentGroup = group;
|
||||
assignGroupViewElements();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
|
||||
@@ -246,65 +372,113 @@ public class GroupActivity extends ListNodesActivity
|
||||
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
|
||||
if (nodeToMove != null)
|
||||
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
|
||||
protected PwGroup retrieveCurrentGroup(Intent intent, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
PwGroupId pwGroupId = null;
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
||||
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
|
||||
} else {
|
||||
if (getIntent() != null)
|
||||
pwGroupId = getIntent().getParcelableExtra(GROUP_ID_KEY);
|
||||
// If it's a search
|
||||
if ( Intent.ACTION_SEARCH.equals(intent.getAction()) ) {
|
||||
return database.search(intent.getStringExtra(SearchManager.QUERY).trim());
|
||||
}
|
||||
// else a real group
|
||||
else {
|
||||
PwGroupId pwGroupId = null;
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
||||
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
|
||||
} else {
|
||||
if (getIntent() != null)
|
||||
pwGroupId = intent.getParcelableExtra(GROUP_ID_KEY);
|
||||
}
|
||||
|
||||
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
|
||||
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
|
||||
|
||||
Log.w(TAG, "Creating tree view");
|
||||
PwGroup currentGroup;
|
||||
if ( pwGroupId == null ) {
|
||||
currentGroup = rootGroup;
|
||||
} else {
|
||||
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
|
||||
Log.w(TAG, "Creating tree view");
|
||||
PwGroup currentGroup;
|
||||
if (pwGroupId == null) {
|
||||
currentGroup = rootGroup;
|
||||
} else {
|
||||
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
|
||||
}
|
||||
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
if (currentGroup != null) {
|
||||
addGroupEnabled = !readOnly;
|
||||
addEntryEnabled = !readOnly;
|
||||
isRoot = (currentGroup == rootGroup);
|
||||
if (!currentGroup.allowAddEntryIfIsRoot())
|
||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||
}
|
||||
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
public void assignGroupViewElements() {
|
||||
// Assign title
|
||||
if (mCurrentGroup != null) {
|
||||
String title = mCurrentGroup.getName();
|
||||
if (title != null && title.length() > 0) {
|
||||
if (groupNameView != null) {
|
||||
groupNameView.setText(title);
|
||||
groupNameView.invalidate();
|
||||
}
|
||||
} 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);
|
||||
if (groupNameView != null) {
|
||||
groupNameView.setText(getText(R.string.root));
|
||||
groupNameView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentGroupIsASearch) {
|
||||
searchTitleView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
searchTitleView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Assign icon
|
||||
if (currentGroupIsASearch) {
|
||||
if (toolbar != null) {
|
||||
toolbar.setNavigationIcon(null);
|
||||
}
|
||||
iconView.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Assign the group icon depending of IconPack or custom icon
|
||||
iconView.setVisibility(View.VISIBLE);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show button if allowed
|
||||
if (addNodeButtonView != null) {
|
||||
|
||||
// To enable add button
|
||||
boolean addGroupEnabled = !readOnly && !currentGroupIsASearch;
|
||||
boolean addEntryEnabled = !readOnly && !currentGroupIsASearch;
|
||||
if (mCurrentGroup != null) {
|
||||
boolean isRoot = (mCurrentGroup == rootGroup);
|
||||
if (!mCurrentGroup.allowAddEntryIfIsRoot())
|
||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||
if (isRoot) {
|
||||
showWarnings();
|
||||
}
|
||||
}
|
||||
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
||||
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
||||
|
||||
if (addNodeButtonView.isEnable())
|
||||
addNodeButtonView.showButton();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,6 +487,50 @@ public class GroupActivity extends ListNodesActivity
|
||||
addNodeButtonView.hideButtonOnScrollListener(dy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
|
||||
// Add event when we have Autofill
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openChildGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
// Build response with the entry selected
|
||||
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( assistStructure == null ){
|
||||
if (entrySelectionMode) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openChildGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openChildGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryActivity.launch(this, (PwEntry) node, readOnly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOpenMenuClick(PwNode node) {
|
||||
onNodeClick(node);
|
||||
@@ -485,9 +703,8 @@ public class GroupActivity extends ListNodesActivity
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Show button on resume
|
||||
if (addNodeButtonView != null)
|
||||
addNodeButtonView.showButton();
|
||||
// Refresh the elements
|
||||
assignGroupViewElements();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -501,7 +718,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
if (listNodesFragment != null
|
||||
&& listNodesFragment.isEmpty()) {
|
||||
if (!PreferencesUtil.isEducationNewNodePerformed(this)
|
||||
&& addNodeButtonView.isVisible()) {
|
||||
&& addNodeButtonView.isEnable()) {
|
||||
|
||||
TapTargetView.showFor(this,
|
||||
TapTarget.forView(findViewById(R.id.add_button),
|
||||
@@ -652,11 +869,26 @@ public class GroupActivity extends ListNodesActivity
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
}
|
||||
if (searchView != null) {
|
||||
// TODO Flickering when locking, will be better with content provider
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, GroupActivity.class)));
|
||||
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||
searchView.setSuggestionsAdapter(searchSuggestionAdapter);
|
||||
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
onNodeClick(searchSuggestionAdapter.getEntryFromPosition(position));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MenuUtil.contributionMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
// Launch education screen
|
||||
@@ -673,7 +905,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||
// manually launch the real search activity
|
||||
final Intent searchIntent = new Intent(getApplicationContext(), SearchResultsActivity.class);
|
||||
final Intent searchIntent = new Intent(getApplicationContext(), GroupActivity.class);
|
||||
// add query to the Intent Extras
|
||||
searchIntent.setAction(Intent.ACTION_SEARCH);
|
||||
searchIntent.putExtra(SearchManager.QUERY, query);
|
||||
@@ -706,7 +938,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
return true;
|
||||
|
||||
case R.id.menu_search:
|
||||
onSearchRequested();
|
||||
//onSearchRequested();
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
@@ -716,8 +948,11 @@ public class GroupActivity extends ListNodesActivity
|
||||
case R.id.menu_change_master_key:
|
||||
setPassword();
|
||||
return true;
|
||||
default:
|
||||
// Check the time lock before launching settings
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setPassword() {
|
||||
@@ -789,6 +1024,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
class AfterAddNode extends AfterActionNodeOnFinish {
|
||||
|
||||
@Override
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
|
||||
@@ -807,6 +1043,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
class AfterUpdateNode extends AfterActionNodeOnFinish {
|
||||
|
||||
@Override
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
|
||||
@@ -892,16 +1129,79 @@ public class GroupActivity extends ListNodesActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openGroup(PwGroup group) {
|
||||
super.openGroup(group);
|
||||
if (addNodeButtonView != null)
|
||||
addNodeButtonView.showButton();
|
||||
public void onAssignKeyDialogPositiveClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogNegativeClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
/*
|
||||
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
||||
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
||||
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
||||
*/
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
int flags = intent.getFlags();
|
||||
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
intent.setFlags(flags);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSearchInIntent(Intent intent) {
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
currentGroupIsASearch = false;
|
||||
intent.setAction(Intent.ACTION_DEFAULT);
|
||||
intent.removeExtra(SearchManager.QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
if (addNodeButtonView != null)
|
||||
addNodeButtonView.showButton();
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
super.onBackPressed();
|
||||
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
// to refresh fragment
|
||||
listNodesFragment.rebuildList();
|
||||
mCurrentGroup = listNodesFragment.getMainGroup();
|
||||
removeSearchInIntent(getIntent());
|
||||
assignGroupViewElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,294 +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.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
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.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||
import com.kunzisoft.keepass.password.AssignPasswordHelper;
|
||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
|
||||
public abstract class ListNodesActivity extends LockingActivity
|
||||
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
NodeAdapter.NodeClickCallback,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
|
||||
protected ListNodesFragment listNodesFragment;
|
||||
|
||||
protected Database database;
|
||||
protected PwGroup rootGroup;
|
||||
protected PwGroup mCurrentGroup;
|
||||
protected TextView groupNameView;
|
||||
|
||||
protected boolean entrySelectionMode;
|
||||
protected AutofillHelper autofillHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
database = App.getDB();
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! database.getLoaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateOptionsMenu();
|
||||
|
||||
rootGroup = database.getPwDatabase().getRootGroup();
|
||||
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
|
||||
|
||||
initializeListNodesFragment(mCurrentGroup);
|
||||
|
||||
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Refresh the title
|
||||
assignToolbarElements();
|
||||
}
|
||||
|
||||
protected void initializeListNodesFragment(PwGroup currentGroup) {
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
if (listNodesFragment == null)
|
||||
listNodesFragment = ListNodesFragment.newInstance(currentGroup, readOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
default:
|
||||
// Check the time lock before launching settings
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
|
||||
// Add event when we have Autofill
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
// Build response with the entry selected
|
||||
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( assistStructure == null ){
|
||||
if (entrySelectionMode) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryActivity.launch(this, (PwEntry) node, readOnly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void openGroup(PwGroup group) {
|
||||
// Check Timeout
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly);
|
||||
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 void onAssignKeyDialogPositiveClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogNegativeClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
/*
|
||||
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
||||
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
||||
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
||||
*/
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
int flags = intent.getFlags();
|
||||
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
intent.setFlags(flags);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
super.onBackPressed();
|
||||
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
// to refresh fragment
|
||||
listNodesFragment.rebuildList();
|
||||
mCurrentGroup = listNodesFragment.getMainGroup();
|
||||
assignToolbarElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,25 +34,30 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
private static final String TAG = ListNodesFragment.class.getName();
|
||||
|
||||
private static final String GROUP_KEY = "GROUP_KEY";
|
||||
private static final String IS_SEARCH = "IS_SEARCH";
|
||||
|
||||
private NodeAdapter.NodeClickCallback nodeClickCallback;
|
||||
private NodeAdapter.NodeMenuListener nodeMenuListener;
|
||||
private OnScrollListener onScrollListener;
|
||||
|
||||
private RecyclerView listView;
|
||||
protected PwGroup mCurrentGroup;
|
||||
protected NodeAdapter mAdapter;
|
||||
private PwGroup currentGroup;
|
||||
private NodeAdapter mAdapter;
|
||||
|
||||
private View notFoundView;
|
||||
private boolean isASearchResult;
|
||||
|
||||
// Preferences for sorting
|
||||
private SharedPreferences prefs;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly) {
|
||||
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
|
||||
Bundle bundle = new Bundle();
|
||||
if (group != null) {
|
||||
bundle.putParcelable(GROUP_KEY, group);
|
||||
}
|
||||
bundle.putBoolean(IS_SEARCH, isASearch);
|
||||
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
|
||||
ListNodesFragment listNodesFragment = new ListNodesFragment();
|
||||
listNodesFragment.setArguments(bundle);
|
||||
@@ -99,11 +104,17 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
if (getArguments() != null) {
|
||||
// Contains all the group in element
|
||||
if (getArguments().containsKey(GROUP_KEY)) {
|
||||
mCurrentGroup = getArguments().getParcelable(GROUP_KEY);
|
||||
currentGroup = getArguments().getParcelable(GROUP_KEY);
|
||||
}
|
||||
|
||||
if (getArguments().containsKey(IS_SEARCH)) {
|
||||
isASearchResult = getArguments().getBoolean(IS_SEARCH);
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater(), readOnly);
|
||||
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
|
||||
mAdapter.setReadOnly(readOnly);
|
||||
mAdapter.setIsASearchResult(isASearchResult);
|
||||
mAdapter.setOnNodeClickListener(nodeClickCallback);
|
||||
|
||||
if (nodeMenuListener != null) {
|
||||
@@ -129,6 +140,7 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
View rootView = inflater.cloneInContext(getContextThemed())
|
||||
.inflate(R.layout.list_nodes_fragment, container, false);
|
||||
listView = rootView.findViewById(R.id.nodes_list);
|
||||
notFoundView = rootView.findViewById(R.id.not_found_container);
|
||||
|
||||
if (onScrollListener != null) {
|
||||
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@@ -148,11 +160,20 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
super.onResume();
|
||||
|
||||
rebuildList();
|
||||
|
||||
if (isASearchResult && mAdapter.isEmpty()) {
|
||||
// To show the " no search entry found "
|
||||
listView.setVisibility(View.GONE);
|
||||
notFoundView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
notFoundView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void rebuildList() {
|
||||
// Add elements to the list
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
mAdapter.rebuildList(currentGroup);
|
||||
assignListToNodeAdapter(listView);
|
||||
}
|
||||
|
||||
@@ -174,7 +195,7 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
|
||||
// Tell the adapter to refresh it's list
|
||||
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
mAdapter.rebuildList(currentGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,7 +253,7 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
mAdapter.addNode(newNode);
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
//mAdapter.updateLastNodeRegister(newNode);
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
mAdapter.rebuildList(currentGroup);
|
||||
}
|
||||
} else {
|
||||
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
|
||||
@@ -259,7 +280,7 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
}
|
||||
|
||||
public PwGroup getMainGroup() {
|
||||
return mCurrentGroup;
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
public interface OnScrollListener {
|
||||
|
||||
@@ -62,6 +62,7 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
private NodeMenuListener nodeMenuListener;
|
||||
private boolean activateContextMenu;
|
||||
private boolean readOnly;
|
||||
private boolean isASearchResult;
|
||||
|
||||
private int iconGroupColor;
|
||||
private int iconEntryColor;
|
||||
@@ -70,13 +71,14 @@ 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, MenuInflater menuInflater, boolean readOnly) {
|
||||
public NodeAdapter(final Context context, MenuInflater menuInflater) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.menuInflater = menuInflater;
|
||||
this.context = context;
|
||||
assignPreferences();
|
||||
this.activateContextMenu = false;
|
||||
this.readOnly = readOnly;
|
||||
this.readOnly = false;
|
||||
this.isASearchResult = false;
|
||||
|
||||
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
|
||||
@Override public int compare(PwNode item1, PwNode item2) {
|
||||
@@ -103,6 +105,14 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
taTextColor.recycle();
|
||||
}
|
||||
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public void setIsASearchResult(boolean isASearchResult) {
|
||||
this.isASearchResult = isASearchResult;
|
||||
}
|
||||
|
||||
public void setActivateContextMenu(boolean activate) {
|
||||
this.activateContextMenu = activate;
|
||||
}
|
||||
@@ -136,6 +146,14 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the adapter contains or not any element
|
||||
* @return true if the list is empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return nodeSortedList.size() <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the list
|
||||
* @param node Node to add
|
||||
@@ -301,29 +319,44 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
||||
menuInflater.inflate(R.menu.node_menu, contextMenu);
|
||||
|
||||
// Opening
|
||||
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
|
||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
|
||||
// Edition
|
||||
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||
contextMenu.removeItem(R.id.menu_edit);
|
||||
contextMenu.removeItem(R.id.menu_copy);
|
||||
contextMenu.removeItem(R.id.menu_move);
|
||||
contextMenu.removeItem(R.id.menu_delete);
|
||||
} else {
|
||||
// Edition
|
||||
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);
|
||||
} else {
|
||||
// TODO COPY For Group
|
||||
contextMenu.removeItem(R.id.menu_copy);
|
||||
}
|
||||
// Move
|
||||
}
|
||||
|
||||
// Copy (not for group)
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())
|
||||
|| node.getType().equals(PwNode.Type.GROUP)) {
|
||||
// TODO COPY For Group
|
||||
contextMenu.removeItem(R.id.menu_copy);
|
||||
} else {
|
||||
menuItem = contextMenu.findItem(R.id.menu_copy);
|
||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
}
|
||||
|
||||
// Move
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||
contextMenu.removeItem(R.id.menu_move);
|
||||
} else {
|
||||
menuItem = contextMenu.findItem(R.id.menu_move);
|
||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
// Deletion
|
||||
}
|
||||
|
||||
// Deletion
|
||||
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||
contextMenu.removeItem(R.id.menu_delete);
|
||||
} else {
|
||||
menuItem = contextMenu.findItem(R.id.menu_delete);
|
||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.database.Database;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwIcon;
|
||||
import com.kunzisoft.keepass.database.PwIconFactory;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
import com.kunzisoft.keepass.database.cursor.EntryCursor;
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SearchEntryCursorAdapter extends CursorAdapter {
|
||||
|
||||
private LayoutInflater cursorInflater;
|
||||
private Database database;
|
||||
private int iconColor;
|
||||
|
||||
public SearchEntryCursorAdapter(Context context, Database database) {
|
||||
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
cursorInflater = (LayoutInflater) context.getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
this.database = database;
|
||||
|
||||
// Get the icon color
|
||||
int[] attrTextColor = {R.attr.textColorInverse};
|
||||
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
|
||||
this.iconColor = taTextColor.getColor(0, Color.WHITE);
|
||||
taTextColor.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
|
||||
View view = cursorInflater.inflate(R.layout.search_entry, parent ,false);
|
||||
ViewHolder viewHolder = new ViewHolder();
|
||||
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon);
|
||||
viewHolder.textViewTitle = view.findViewById(R.id.entry_text);
|
||||
view.setTag(viewHolder);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
|
||||
// Retrieve elements from cursor
|
||||
PwIconFactory iconFactory = database.getPwDatabase().getIconFactory();
|
||||
PwIcon icon = iconFactory.getIcon(
|
||||
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||
if (icon.isUnknown()) {
|
||||
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
|
||||
if (icon.isUnknown())
|
||||
icon = iconFactory.getKeyIcon();
|
||||
}
|
||||
String title = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE) );
|
||||
|
||||
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||
|
||||
// Assign image
|
||||
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
||||
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, true, iconColor);
|
||||
} else {
|
||||
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon);
|
||||
}
|
||||
// Assign title
|
||||
viewHolder.textViewTitle.setText(title);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
ImageView imageViewIcon;
|
||||
TextView textViewTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
|
||||
return database.searchEntry(constraint.toString());
|
||||
}
|
||||
|
||||
public PwEntry getEntryFromPosition(int position) {
|
||||
PwEntry pwEntry = null;
|
||||
|
||||
Cursor cursor = this.getCursor();
|
||||
if (cursor.moveToFirst()
|
||||
&&
|
||||
cursor.move(position)) {
|
||||
|
||||
pwEntry = database.createEntry();
|
||||
database.populateEntry(pwEntry, cursor);
|
||||
|
||||
}
|
||||
return pwEntry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,19 +21,21 @@ package com.kunzisoft.keepass.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||
import com.kunzisoft.keepass.database.cursor.EntryCursor;
|
||||
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||
import com.kunzisoft.keepass.database.load.Importer;
|
||||
import com.kunzisoft.keepass.database.load.ImporterFactory;
|
||||
import com.kunzisoft.keepass.database.save.PwDbOutput;
|
||||
import com.kunzisoft.keepass.database.search.SearchDbHelper;
|
||||
import com.kunzisoft.keepass.icons.IconDrawableFactory;
|
||||
import com.kunzisoft.keepass.search.SearchDbHelper;
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
|
||||
import com.kunzisoft.keepass.utils.UriUtil;
|
||||
|
||||
@@ -179,13 +181,17 @@ public class Database {
|
||||
}
|
||||
|
||||
public PwGroup search(String str) {
|
||||
return search(str, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public PwGroup search(String str, int max) {
|
||||
if (searchHelper == null) { return null; }
|
||||
try {
|
||||
switch (pm.getVersion()) {
|
||||
case V3:
|
||||
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str);
|
||||
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str, max);
|
||||
case V4:
|
||||
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str);
|
||||
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str, max);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Search can't be performed with this SearchHelper", e);
|
||||
@@ -193,6 +199,46 @@ public class Database {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor searchEntry(String query) {
|
||||
final EntryCursor cursor = new EntryCursor();
|
||||
|
||||
// TODO real content provider
|
||||
if (!query.isEmpty()) {
|
||||
PwGroup searchResult = search(query, 6);
|
||||
for (int i=0; i < searchResult.numbersOfChildEntries(); i++) {
|
||||
PwEntry entry = searchResult.getChildEntryAt(i);
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
EntryCursor.addEntry(cursor, (PwEntryV3) entry, i);
|
||||
case V4:
|
||||
EntryCursor.addEntry(cursor, (PwEntryV4) entry, i);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwGroup can't be populated", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public void populateEntry(PwEntry pwEntry, Cursor cursor) {
|
||||
// TODO invert field reference manager
|
||||
pwEntry.startToManageFieldReferences(getPwDatabase());
|
||||
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
EntryCursor.populateEntry(cursor, (PwEntryV3) pwEntry, iconFactory);
|
||||
case V4:
|
||||
EntryCursor.populateEntry(cursor, (PwEntryV4) pwEntry, iconFactory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwGroup can't be populated", e);
|
||||
}
|
||||
pwEntry.stopToManageFieldReferences();
|
||||
}
|
||||
|
||||
public void saveData(Context ctx) throws IOException, PwDbOutputException {
|
||||
saveData(ctx, mUri);
|
||||
}
|
||||
@@ -464,7 +510,11 @@ public class Database {
|
||||
}
|
||||
}
|
||||
|
||||
public PwEntry createEntry(PwGroup parent) {
|
||||
public PwEntry createEntry() {
|
||||
return createEntry(null);
|
||||
}
|
||||
|
||||
public PwEntry createEntry(@Nullable PwGroup parent) {
|
||||
PwEntry newPwEntry = null;
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
|
||||
@@ -76,7 +76,7 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
|
||||
}
|
||||
|
||||
public void startToManageFieldReferences(PwDatabase db) {}
|
||||
public void endToManageFieldReferences() {}
|
||||
public void stopToManageFieldReferences() {}
|
||||
|
||||
public abstract String getTitle();
|
||||
public abstract void setTitle(String title);
|
||||
|
||||
@@ -169,7 +169,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endToManageFieldReferences() {
|
||||
public void stopToManageFieldReferences() {
|
||||
this.mDatabase = null;
|
||||
this.mDecodeRef = false;
|
||||
}
|
||||
@@ -205,41 +205,31 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
||||
|
||||
@Override
|
||||
public void setTitle(String title) {
|
||||
PwDatabaseV4 db = mDatabase;
|
||||
boolean protect = db.getMemoryProtection().protectTitle;
|
||||
|
||||
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectTitle;
|
||||
setProtectedString(STR_TITLE, title, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String user) {
|
||||
PwDatabaseV4 db = mDatabase;
|
||||
boolean protect = db.getMemoryProtection().protectUserName;
|
||||
|
||||
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUserName;
|
||||
setProtectedString(STR_USERNAME, user, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String pass) {
|
||||
PwDatabaseV4 db = mDatabase;
|
||||
boolean protect = db.getMemoryProtection().protectPassword;
|
||||
|
||||
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectPassword;
|
||||
setProtectedString(STR_PASSWORD, pass, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUrl(String url) {
|
||||
PwDatabaseV4 db = mDatabase;
|
||||
boolean protect = db.getMemoryProtection().protectUrl;
|
||||
|
||||
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUrl;
|
||||
setProtectedString(STR_URL, url, protect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotes(String notes) {
|
||||
PwDatabaseV4 db = mDatabase;
|
||||
boolean protect = db.getMemoryProtection().protectNotes;
|
||||
|
||||
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectNotes;
|
||||
setProtectedString(STR_NOTES, notes, protect);
|
||||
}
|
||||
|
||||
@@ -287,7 +277,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
||||
|
||||
@Override
|
||||
public PwIcon getIcon() {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
||||
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
|
||||
return super.getIcon();
|
||||
} else {
|
||||
return customIcon;
|
||||
|
||||
@@ -225,7 +225,7 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
||||
|
||||
@Override
|
||||
public PwIcon getIcon() {
|
||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
||||
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
|
||||
return super.getIcon();
|
||||
} else {
|
||||
return customIcon;
|
||||
|
||||
@@ -32,6 +32,8 @@ public abstract class PwIcon implements Parcelable {
|
||||
|
||||
protected PwIcon(Parcel in) {}
|
||||
|
||||
public abstract boolean isUnknown();
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
||||
@@ -26,8 +26,8 @@ import java.util.UUID;
|
||||
public class PwIconCustom extends PwIcon {
|
||||
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
|
||||
|
||||
public final UUID uuid;
|
||||
public byte[] imageData;
|
||||
private final UUID uuid;
|
||||
private byte[] imageData;
|
||||
|
||||
public PwIconCustom(UUID uuid, byte[] data) {
|
||||
super();
|
||||
@@ -48,6 +48,23 @@ public class PwIconCustom extends PwIcon {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnknown() {
|
||||
return uuid == null || this.equals(ZERO);
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public byte[] getImageData() {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
public void setImageData(byte[] imageData) {
|
||||
this.imageData = imageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeSerializable(uuid);
|
||||
dest.writeByteArray(imageData);
|
||||
|
||||
@@ -46,8 +46,8 @@ public class PwIconFactory {
|
||||
}
|
||||
|
||||
public PwIconStandard getFolderIcon() {
|
||||
return getIcon(PwIconStandard.FOLDER);
|
||||
}
|
||||
return getIcon(PwIconStandard.FOLDER);
|
||||
}
|
||||
|
||||
public PwIconStandard getIcon(int iconId) {
|
||||
PwIconStandard icon = (PwIconStandard) cache.get(iconId);
|
||||
@@ -71,25 +71,8 @@ public class PwIconFactory {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public PwIconCustom getIcon(UUID iconUuid, byte[] data) {
|
||||
PwIconCustom icon = (PwIconCustom) customCache.get(iconUuid);
|
||||
|
||||
if (icon == null) {
|
||||
icon = new PwIconCustom(iconUuid, data);
|
||||
customCache.put(iconUuid, icon);
|
||||
} else {
|
||||
icon.imageData = data;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIconData(UUID iconUuid, byte[] data) {
|
||||
getIcon(iconUuid, data);
|
||||
}
|
||||
|
||||
public void put(PwIconCustom icon) {
|
||||
customCache.put(icon.uuid, icon);
|
||||
customCache.put(icon.getUUID(), icon);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,12 +22,17 @@ package com.kunzisoft.keepass.database;
|
||||
import android.os.Parcel;
|
||||
|
||||
public class PwIconStandard extends PwIcon {
|
||||
public final int iconId;
|
||||
private final int iconId;
|
||||
|
||||
public static final int UNKNOWN = -1;
|
||||
public static final int KEY = 0;
|
||||
public static final int TRASH = 43;
|
||||
public static final int FOLDER = 48;
|
||||
|
||||
public PwIconStandard() {
|
||||
this.iconId = KEY;
|
||||
}
|
||||
|
||||
public PwIconStandard(int iconId) {
|
||||
this.iconId = iconId;
|
||||
}
|
||||
@@ -42,6 +47,15 @@ public class PwIconStandard extends PwIcon {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnknown() {
|
||||
return iconId == UNKNOWN;
|
||||
}
|
||||
|
||||
public int getIconId() {
|
||||
return iconId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(iconId);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.joda.time.LocalDate;
|
||||
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Parcelable, Cloneable {
|
||||
|
||||
protected Parent parent = null;
|
||||
protected PwIconStandard icon = new PwIconStandard(0);
|
||||
protected PwIconStandard icon = new PwIconStandard();
|
||||
protected PwDate creation = new PwDate();
|
||||
protected PwDate lastMod = new PwDate();
|
||||
protected PwDate lastAccess = new PwDate();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.kunzisoft.keepass.database.cursor;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.provider.BaseColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.PwIconCustom;
|
||||
import com.kunzisoft.keepass.database.PwIconFactory;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntryCursor extends MatrixCursor {
|
||||
|
||||
public static final String _ID = BaseColumns._ID;
|
||||
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUIDMostSignificantBits";
|
||||
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUIDLeastSignificantBits";
|
||||
public static final String COLUMN_INDEX_TITLE = "title";
|
||||
public static final String COLUMN_INDEX_ICON_STANDARD = "iconStandard";
|
||||
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "iconCustomUUIDMostSignificantBits";
|
||||
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "iconCustomUUIDLeastSignificantBits";
|
||||
|
||||
public EntryCursor() {
|
||||
super(new String[]{ _ID,
|
||||
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_TITLE,
|
||||
COLUMN_INDEX_ICON_STANDARD,
|
||||
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS});
|
||||
}
|
||||
|
||||
public static void addEntry(MatrixCursor cursor, PwEntryV3 entry, int id) {
|
||||
cursor.addRow(new Object[] {id,
|
||||
entry.getUUID().getMostSignificantBits(),
|
||||
entry.getUUID().getLeastSignificantBits(),
|
||||
entry.getTitle(),
|
||||
entry.getIconStandard().getIconId(),
|
||||
PwDatabase.UUID_ZERO.getMostSignificantBits(),
|
||||
PwDatabase.UUID_ZERO.getLeastSignificantBits()});
|
||||
}
|
||||
|
||||
public static void addEntry(MatrixCursor cursor, PwEntryV4 entry, int id) {
|
||||
cursor.addRow(new Object[] {id,
|
||||
entry.getUUID().getMostSignificantBits(),
|
||||
entry.getUUID().getLeastSignificantBits(),
|
||||
entry.getTitle(),
|
||||
entry.getIconStandard().getIconId(),
|
||||
entry.getCustomIcon().getUUID().getMostSignificantBits(),
|
||||
entry.getCustomIcon().getUUID().getLeastSignificantBits()});
|
||||
}
|
||||
|
||||
private static void populateEntryBaseVersion(Cursor cursor, PwEntry pwEntry, PwIconFactory iconFactory) {
|
||||
pwEntry.setUUID(
|
||||
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||
pwEntry.setTitle(cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
|
||||
|
||||
PwIconStandard iconStandard = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
|
||||
pwEntry.setIcon(iconStandard);
|
||||
}
|
||||
|
||||
public static void populateEntry(Cursor cursor, PwEntryV3 pwEntry, PwIconFactory iconFactory) {
|
||||
populateEntryBaseVersion(cursor, pwEntry, iconFactory);
|
||||
}
|
||||
|
||||
public static void populateEntry(Cursor cursor, PwEntryV4 pwEntry, PwIconFactory iconFactory) {
|
||||
populateEntryBaseVersion(cursor, pwEntry, iconFactory);
|
||||
|
||||
PwIconCustom iconCustom = iconFactory.getIcon(
|
||||
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||
pwEntry.setCustomIcon(iconCustom);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.SearchParameters;
|
||||
import com.kunzisoft.keepass.database.SearchParametersV4;
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters;
|
||||
import com.kunzisoft.keepass.database.search.SearchParametersV4;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.iterator;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||
import com.kunzisoft.keepass.database.SearchParameters;
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.iterator;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.SearchParametersV4;
|
||||
import com.kunzisoft.keepass.database.search.SearchParametersV4;
|
||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -78,18 +78,19 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
|
||||
}
|
||||
|
||||
private boolean searchInField(String key) {
|
||||
if (key.equals(PwEntryV4.STR_TITLE)) {
|
||||
return sp.searchInTitles;
|
||||
} else if (key.equals(PwEntryV4.STR_USERNAME)) {
|
||||
return sp.searchInUserNames;
|
||||
} else if (key.equals(PwEntryV4.STR_PASSWORD)) {
|
||||
return sp.searchInPasswords;
|
||||
} else if (key.equals(PwEntryV4.STR_URL)) {
|
||||
return sp.searchInUrls;
|
||||
} else if (key.equals(PwEntryV4.STR_NOTES)) {
|
||||
return sp.searchInNotes;
|
||||
} else {
|
||||
return sp.searchInOther;
|
||||
switch (key) {
|
||||
case PwEntryV4.STR_TITLE:
|
||||
return sp.searchInTitles;
|
||||
case PwEntryV4.STR_USERNAME:
|
||||
return sp.searchInUserNames;
|
||||
case PwEntryV4.STR_PASSWORD:
|
||||
return sp.searchInPasswords;
|
||||
case PwEntryV4.STR_URL:
|
||||
return sp.searchInUrls;
|
||||
case PwEntryV4.STR_NOTES:
|
||||
return sp.searchInNotes;
|
||||
default:
|
||||
return sp.searchInOther;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -350,10 +350,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
||||
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
|
||||
writeObject(PwDatabaseV4XML.ElemName, group.getName());
|
||||
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
|
||||
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().iconId);
|
||||
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().getIconId());
|
||||
|
||||
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().uuid);
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().getUUID());
|
||||
}
|
||||
|
||||
writeList(PwDatabaseV4XML.ElemTimes, group);
|
||||
@@ -375,10 +375,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
||||
xml.startTag(null, PwDatabaseV4XML.ElemEntry);
|
||||
|
||||
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
|
||||
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().iconId);
|
||||
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().getIconId());
|
||||
|
||||
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().uuid);
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().getUUID());
|
||||
}
|
||||
|
||||
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
|
||||
@@ -701,8 +701,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
||||
for (PwIconCustom icon : customIcons) {
|
||||
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
||||
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid);
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.imageData)));
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUUID());
|
||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData())));
|
||||
|
||||
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public class PwEntryOutputV3 {
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(LONG_FOUR);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().getIconId()));
|
||||
|
||||
// Title
|
||||
//byte[] title = mPE.title.getBytes("UTF-8");
|
||||
|
||||
@@ -93,7 +93,7 @@ public class PwGroupOutputV3 {
|
||||
// Image ID
|
||||
mOS.write(IMAGEID_FIELD_TYPE);
|
||||
mOS.write(IMAGEID_FIELD_SIZE);
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
|
||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().getIconId()));
|
||||
|
||||
// Level
|
||||
mOS.write(LEVEL_FIELD_TYPE);
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import com.kunzisoft.keepass.database.EntryHandler;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -17,7 +17,10 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import com.kunzisoft.keepass.database.EntryHandler;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -17,8 +17,11 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.utils.StrUtil;
|
||||
import com.kunzisoft.keepass.utils.UuidUtil;
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import com.kunzisoft.keepass.database.EntryHandler;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.PwGroupV4;
|
||||
import com.kunzisoft.keepass.utils.StrUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.search;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -54,13 +54,12 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
|
||||
private boolean omitBackup() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
|
||||
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
|
||||
|
||||
}
|
||||
|
||||
public PwGroupSearch search(PwDatabaseVersion pm, String qStr) {
|
||||
public PwGroupSearch search(PwDatabaseVersion pm, String qStr, int max) {
|
||||
|
||||
PwGroupSearch group = pm.createGroup();
|
||||
group.setName(mCtx.getString(R.string.search_results));
|
||||
group.setName("\"" + qStr + "\"");
|
||||
group.setEntries(new ArrayList<>());
|
||||
|
||||
// Search all entries
|
||||
@@ -68,6 +67,7 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
|
||||
qStr = qStr.toLowerCase(loc);
|
||||
boolean isOmitBackup = omitBackup();
|
||||
|
||||
// TODO Search from the current group
|
||||
Queue<PwGroupSearch> worklist = new LinkedList<>();
|
||||
if (pm.getRootGroup() != null) {
|
||||
worklist.add(pm.getRootGroup());
|
||||
@@ -79,6 +79,8 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
|
||||
if (pm.isGroupSearchable(top, isOmitBackup)) {
|
||||
for (PwEntrySearch entry : top.getChildEntries()) {
|
||||
processEntries(entry, group.getChildEntries(), qStr, loc);
|
||||
if (group.numbersOfChildEntries() >= max)
|
||||
return group;
|
||||
}
|
||||
|
||||
for (PwGroupSearch childGroup : top.getChildGroups()) {
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
/**
|
||||
* @author bpellin
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
public class SearchParametersV4 extends SearchParameters implements Cloneable {
|
||||
public static SearchParametersV4 DEFAULT = new SearchParametersV4();
|
||||
@@ -221,7 +221,7 @@ public class IconDrawableFactory {
|
||||
* @return The drawable
|
||||
*/
|
||||
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
||||
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId);
|
||||
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
|
||||
|
||||
return getIconDrawable(context, resId, isTint, tintColor);
|
||||
}
|
||||
@@ -289,14 +289,14 @@ public class IconDrawableFactory {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
|
||||
Drawable draw = (Drawable) customIconMap.get(icon.getUUID());
|
||||
|
||||
if (draw == null) {
|
||||
if (icon.imageData == null) {
|
||||
if (icon.getImageData() == null) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
|
||||
|
||||
// Could not understand custom icon
|
||||
if (bitmap == null) {
|
||||
@@ -306,7 +306,7 @@ public class IconDrawableFactory {
|
||||
bitmap = resize(bitmap);
|
||||
|
||||
draw = new BitmapDrawable(context.getResources(), bitmap);
|
||||
customIconMap.put(icon.uuid, draw);
|
||||
customIconMap.put(icon.getUUID(), draw);
|
||||
}
|
||||
|
||||
return draw;
|
||||
|
||||
@@ -1,114 +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.search;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.activities.ListNodesActivity;
|
||||
import com.kunzisoft.keepass.activities.ListNodesFragment;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class SearchResultsActivity extends ListNodesActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.search_label));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
groupNameView = findViewById(R.id.group_name);
|
||||
|
||||
attachFragmentToContentView();
|
||||
|
||||
View notFoundView = findViewById(R.id.not_found_container);
|
||||
View listContainer = findViewById(R.id.nodes_list_fragment_container);
|
||||
|
||||
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
|
||||
listContainer.setVisibility(View.GONE);
|
||||
notFoundView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
listContainer.setVisibility(View.VISIBLE);
|
||||
notFoundView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeListNodesFragment(PwGroup currentGroup) {
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
// Directly get group and not id
|
||||
if (listNodesFragment == null)
|
||||
listNodesFragment = ListNodesFragment.newInstance(currentGroup, readOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! database.getLoaded() ) {
|
||||
finish();
|
||||
}
|
||||
return database.search(getSearchStr(getIntent()).trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.contributionMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private String getSearchStr(Intent queryIntent) {
|
||||
// get and process search query here
|
||||
final String queryAction = queryIntent.getAction();
|
||||
if ( Intent.ACTION_SEARCH.equals(queryAction) ) {
|
||||
return queryIntent.getStringExtra(SearchManager.QUERY);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,12 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils;
|
||||
|
||||
import com.kunzisoft.keepass.database.EntrySearchV4;
|
||||
import com.kunzisoft.keepass.database.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV4;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.PwGroupV4;
|
||||
import com.kunzisoft.keepass.database.SearchParametersV4;
|
||||
import com.kunzisoft.keepass.database.search.EntrySearchV4;
|
||||
import com.kunzisoft.keepass.database.search.SearchParametersV4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -158,7 +157,7 @@ public class SprEngineV4 {
|
||||
|
||||
List<PwEntryV4> list = new ArrayList<>();
|
||||
// TODO type parameter
|
||||
EntrySearchV4 entrySearchV4 = new EntrySearchV4((PwGroupV4) ctx.db.getRootGroup());
|
||||
EntrySearchV4 entrySearchV4 = new EntrySearchV4(ctx.db.getRootGroup());
|
||||
entrySearchV4.searchEntries(sp, list);
|
||||
|
||||
if (list.size() > 0) {
|
||||
|
||||
@@ -189,7 +189,7 @@ public class AddNodeButtonView extends RelativeLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
public boolean isEnable() {
|
||||
return getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +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.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
|
||||
public class GroupHeaderView extends RelativeLayout {
|
||||
|
||||
public GroupHeaderView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GroupHeaderView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
inflate(context);
|
||||
}
|
||||
|
||||
private void inflate(Context context) {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
assert inflater != null;
|
||||
inflater.inflate(R.layout.group_header, this);
|
||||
|
||||
if (App.getDB().isReadOnly()) {
|
||||
View readOnlyIndicator = findViewById(R.id.read_only);
|
||||
readOnlyIndicator.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
24
app/src/main/res/anim/slide_in_bottom.xml
Normal file
24
app/src/main/res/anim/slide_in_bottom.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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:fromYDelta="100%p" android:toYDelta="0"
|
||||
android:duration="@integer/animation_duration"/>
|
||||
</set>
|
||||
24
app/src/main/res/anim/slide_in_top.xml
Normal file
24
app/src/main/res/anim/slide_in_top.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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:fromYDelta="-100%p" android:toYDelta="0"
|
||||
android:duration="@integer/animation_duration"/>
|
||||
</set>
|
||||
24
app/src/main/res/anim/slide_out_bottom.xml
Normal file
24
app/src/main/res/anim/slide_out_bottom.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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:fromYDelta="0" android:toYDelta="100%p"
|
||||
android:duration="@integer/animation_duration"/>
|
||||
</set>
|
||||
24
app/src/main/res/anim/slide_out_top.xml
Normal file
24
app/src/main/res/anim/slide_out_top.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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:fromYDelta="0" android:toYDelta="-100%p"
|
||||
android:duration="@integer/animation_duration"/>
|
||||
</set>
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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/>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/read_only"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/read_only"
|
||||
android:visibility="gone"
|
||||
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/list_margin"
|
||||
android:layout_marginEnd="@dimen/list_margin">
|
||||
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitXY" />
|
||||
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/root"
|
||||
android:maxLines="1"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -40,6 +40,7 @@
|
||||
android:id="@+id/entry_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:lines="1"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Default"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
<?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"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/windowBackground" />
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:id="@+id/not_found_container"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/not_found_img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/img_not_found"/>
|
||||
<TextView
|
||||
android:id="@+id/not_found_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/no_results"/>
|
||||
</LinearLayout>
|
||||
<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" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -56,13 +56,43 @@
|
||||
app:popupTheme="?attr/toolbarPopupAppearance"
|
||||
android:elevation="4dp"
|
||||
tools:targetApi="lollipop">
|
||||
<com.kunzisoft.keepass.view.GroupHeaderView
|
||||
<LinearLayout
|
||||
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:layout_below="@+id/toolbar"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/search_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/search_results"
|
||||
android:visibility="gone"
|
||||
style="@style/KeepassDXStyle.TextAppearance.DefaultTextOnPrimary" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/list_margin"
|
||||
android:layout_marginEnd="@dimen/list_margin">
|
||||
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitXY" />
|
||||
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/root"
|
||||
android:maxLines="1"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
@@ -70,10 +100,11 @@
|
||||
|
||||
<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" />
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_below="@+id/toolbar"
|
||||
android:background="?android:attr/windowBackground" />
|
||||
|
||||
<com.kunzisoft.keepass.view.AddNodeButtonView
|
||||
android:id="@+id/add_node_button"
|
||||
|
||||
51
app/src/main/res/layout/search_entry.xml
Normal file
51
app/src/main/res/layout/search_entry.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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/>.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/entry_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minHeight="32dp"
|
||||
android:background="?attr/colorPrimary" >
|
||||
<android.support.v7.widget.AppCompatImageView android:id="@+id/entry_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginLeft="@dimen/default_margin"
|
||||
android:layout_marginStart="@dimen/default_margin"
|
||||
android:layout_marginRight="@dimen/default_margin"
|
||||
android:layout_marginEnd="@dimen/default_margin"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true" />
|
||||
<TextView
|
||||
android:id="@+id/entry_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:lines="1"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Inverse"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_toRightOf="@+id/entry_icon"
|
||||
android:layout_toEndOf="@+id/entry_icon" />
|
||||
</RelativeLayout>
|
||||
Reference in New Issue
Block a user