diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java
deleted file mode 100644
index 5ee760dcb..000000000
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java
+++ /dev/null
@@ -1,567 +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 .
- *
- */
-package com.kunzisoft.keepass.activities;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v7.widget.Toolbar;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.Toast;
-
-import com.kunzisoft.keepass.R;
-import com.kunzisoft.keepass.activities.lock.LockingHideActivity;
-import com.kunzisoft.keepass.app.App;
-import com.kunzisoft.keepass.database.action.node.ActionNodeValues;
-import com.kunzisoft.keepass.database.action.node.AddEntryRunnable;
-import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable;
-import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
-import com.kunzisoft.keepass.database.element.Database;
-import com.kunzisoft.keepass.database.element.EntryVersioned;
-import com.kunzisoft.keepass.database.element.GroupVersioned;
-import com.kunzisoft.keepass.database.element.PwDate;
-import com.kunzisoft.keepass.database.element.PwIcon;
-import com.kunzisoft.keepass.database.element.PwIconStandard;
-import com.kunzisoft.keepass.database.element.PwNodeId;
-import com.kunzisoft.keepass.database.element.security.ProtectedString;
-import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
-import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
-import com.kunzisoft.keepass.education.EntryEditActivityEducation;
-import com.kunzisoft.keepass.settings.PreferencesUtil;
-import com.kunzisoft.keepass.tasks.ActionRunnable;
-import com.kunzisoft.keepass.timeout.TimeoutHelper;
-import com.kunzisoft.keepass.utils.MenuUtil;
-import com.kunzisoft.keepass.utils.Util;
-import com.kunzisoft.keepass.view.EntryEditCustomField;
-
-import org.jetbrains.annotations.NotNull;
-
-import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD;
-
-public class EntryEditActivity extends LockingHideActivity
- implements IconPickerDialogFragment.IconPickerListener,
- GeneratePasswordDialogFragment.GeneratePasswordListener {
-
- private static final String TAG = EntryEditActivity.class.getName();
-
- // Keys for current Activity
- public static final String KEY_ENTRY = "entry";
- public static final String KEY_PARENT = "parent";
-
- // Keys for callback
- public static final int ADD_ENTRY_RESULT_CODE = 31;
- public static final int UPDATE_ENTRY_RESULT_CODE = 32;
- public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
- public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
-
- private Database database;
-
- protected EntryVersioned mEntry;
- protected GroupVersioned mParent;
- protected EntryVersioned mNewEntry;
- protected boolean mIsNew;
- protected PwIconStandard mSelectedIconStandard;
-
- // Views
- private ScrollView scrollView;
- private EditText entryTitleView;
- private ImageView entryIconView;
- private EditText entryUserNameView;
- private EditText entryUrlView;
- private EditText entryPasswordView;
- private EditText entryConfirmationPasswordView;
- private View generatePasswordView;
- private EditText entryCommentView;
- private ViewGroup entryExtraFieldsContainer;
- private View addNewFieldView;
- private View saveView;
- private int iconColor;
-
- // Education
- private EntryEditActivityEducation entryEditActivityEducation;
-
- /**
- * Launch EntryEditActivity to update an existing entry
- *
- * @param activity from activity
- * @param pwEntry Entry to update
- */
- public static void launch(Activity activity, EntryVersioned pwEntry) {
- if (TimeoutHelper.INSTANCE.checkTimeAndLockIfTimeout(activity)) {
- Intent intent = new Intent(activity, EntryEditActivity.class);
- intent.putExtra(KEY_ENTRY, pwEntry.getNodeId());
- activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
- }
- }
-
- /**
- * Launch EntryEditActivity to add a new entry
- *
- * @param activity from activity
- * @param pwGroup Group who will contains new entry
- */
- public static void launch(Activity activity, GroupVersioned pwGroup) {
- if (TimeoutHelper.INSTANCE.checkTimeAndLockIfTimeout(activity)) {
- Intent intent = new Intent(activity, EntryEditActivity.class);
- intent.putExtra(KEY_PARENT, pwGroup.getNodeId());
- activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.entry_edit);
-
- Toolbar toolbar = findViewById(R.id.toolbar);
- setSupportActionBar(toolbar);
- assert getSupportActionBar() != null;
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- getSupportActionBar().setDisplayShowHomeEnabled(true);
-
- scrollView = findViewById(R.id.entry_edit_scroll);
- scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
-
- entryTitleView = findViewById(R.id.entry_edit_title);
- entryIconView = findViewById(R.id.entry_edit_icon_button);
- entryUserNameView = findViewById(R.id.entry_edit_user_name);
- entryUrlView = findViewById(R.id.entry_edit_url);
- entryPasswordView = findViewById(R.id.entry_edit_password);
- entryConfirmationPasswordView = findViewById(R.id.entry_edit_confirmation_password);
- entryCommentView = findViewById(R.id.entry_edit_notes);
- entryExtraFieldsContainer = findViewById(R.id.entry_edit_advanced_container);
-
- // Focus view to reinitialize timeout
- resetAppTimeoutWhenViewFocusedOrChanged(
- entryTitleView,
- entryIconView,
- entryUserNameView,
- entryUrlView,
- entryPasswordView,
- entryConfirmationPasswordView,
- entryCommentView,
- entryExtraFieldsContainer);
-
- // Likely the app has been killed exit the activity
- database = App.Companion.getCurrentDatabase();
-
- // Retrieve the textColor to tint the icon
- int[] attrs = {android.R.attr.textColorPrimary};
- TypedArray ta = getTheme().obtainStyledAttributes(attrs);
- iconColor = ta.getColor(0, Color.WHITE);
-
- mSelectedIconStandard = database.getIconFactory().getUnknownIcon();
-
- Intent intent = getIntent();
- // Entry is retrieve, it's an entry to update
- PwNodeId keyEntry = intent.getParcelableExtra(KEY_ENTRY);
- if (keyEntry != null) {
- mIsNew = false;
- mEntry = database.getEntryById(keyEntry);
- if (mEntry != null) {
- mParent = mEntry.getParent();
- fillData();
- }
- }
-
- // Parent is retrieve, it's a new entry to create
- PwNodeId keyParent = intent.getParcelableExtra(KEY_PARENT);
- if (keyParent != null) {
- mIsNew = true;
- mEntry = database.createEntry();
- mParent = database.getGroupById(keyParent);
- // Add the default icon
- database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor);
- }
-
- // Close the activity if entry or parent can't be retrieve
- if (mEntry == null || mParent == null) {
- finish();
- return;
- }
-
- // Assign title
- setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry));
-
- // Retrieve the icon after an orientation change
- if (savedInstanceState != null
- && savedInstanceState.containsKey(KEY_ICON_STANDARD)) {
- iconPicked(savedInstanceState);
- }
-
- // Add listener to the icon
- entryIconView.setOnClickListener(v ->
- IconPickerDialogFragment.launch(EntryEditActivity.this));
-
- // Generate password button
- generatePasswordView = findViewById(R.id.entry_edit_generate_button);
- generatePasswordView.setOnClickListener(v -> openPasswordGenerator());
-
- // Save button
- saveView = findViewById(R.id.entry_edit_save);
- saveView.setOnClickListener(v -> saveEntry());
-
- if (mEntry.allowExtraFields()) {
- addNewFieldView = findViewById(R.id.entry_edit_add_new_field);
- addNewFieldView.setVisibility(View.VISIBLE);
- addNewFieldView.setOnClickListener(v -> addNewCustomField());
- }
-
- // Verify the education views
- entryEditActivityEducation = new EntryEditActivityEducation(this);
- new Handler().post(() -> performedNextEducation(entryEditActivityEducation));
- }
-
- private void performedNextEducation(EntryEditActivityEducation entryEditActivityEducation) {
- if (entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
- generatePasswordView,
- tapTargetView -> {
- openPasswordGenerator();
- return null;
- },
- tapTargetView -> {
- performedNextEducation(entryEditActivityEducation);
- return null;
- }
- ));
- else if (mEntry.allowExtraFields()
- && !mEntry.containsCustomFields()
- && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
- addNewFieldView,
- tapTargetView -> {
- addNewCustomField();
- return null;
- },
- tapTargetView -> null)
- );
- }
-
- /**
- * Open the password generator fragment
- */
- private void openPasswordGenerator() {
- GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
- generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
- }
-
- /**
- * Add a new view to fill in the information of the customized field
- */
- private void addNewCustomField() {
- EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this);
- entryEditCustomField.setData("", new ProtectedString(false, ""));
- boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
- entryEditCustomField.setFontVisibility(visibilityFontActivated);
- entryExtraFieldsContainer.addView(entryEditCustomField);
-
- // Scroll bottom
- scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
- }
-
- /**
- * Saves the new entry or update an existing entry in the database
- */
- private void saveEntry() {
- if (!validateBeforeSaving()) {
- return;
- }
- // Clone the entry
- mNewEntry = new EntryVersioned(mEntry);
-
- populateEntryWithViewInfo(mNewEntry);
-
- // Open a progress dialog and save entry
- ActionRunnable task;
- AfterActionNodeFinishRunnable afterActionNodeFinishRunnable =
- new AfterActionNodeFinishRunnable() {
- @Override
- public void onActionNodeFinish(@NotNull ActionNodeValues actionNodeValues) {
- if (actionNodeValues.getSuccess())
- finish();
- }
- };
- if ( mIsNew ) {
- task = new AddEntryRunnable(EntryEditActivity.this,
- database,
- mNewEntry,
- mParent,
- afterActionNodeFinishRunnable,
- !getReadOnly());
- } else {
- task = new UpdateEntryRunnable(EntryEditActivity.this,
- database,
- mEntry,
- mNewEntry,
- afterActionNodeFinishRunnable,
- !getReadOnly());
- }
- new Thread(task).start();
- }
-
-
-
- /**
- * Utility class to retrieve a validation or an error with a message
- */
- private class ErrorValidation {
- static final int unknownMessage = -1;
-
- boolean isValidate = false;
- int messageId = unknownMessage;
-
- void showValidationErrorIfNeeded() {
- if (!isValidate && messageId != unknownMessage)
- Toast.makeText(EntryEditActivity.this, messageId, Toast.LENGTH_LONG).show();
- }
- }
-
- /**
- * Validate or not the entry form
- *
- * @return ErrorValidation An error with a message or a validation without message
- */
- protected ErrorValidation validate() {
- ErrorValidation errorValidation = new ErrorValidation();
-
- // Require title
- String title = entryTitleView.getText().toString();
- if ( title.length() == 0 ) {
- errorValidation.messageId = R.string.error_title_required;
- return errorValidation;
- }
-
- // Validate password
- String pass = entryPasswordView.getText().toString();
- String conf = entryConfirmationPasswordView.getText().toString();
- if ( ! pass.equals(conf) ) {
- errorValidation.messageId = R.string.error_pass_match;
- return errorValidation;
- }
-
- // Validate extra fields
- if (mEntry.allowExtraFields()) {
- for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
- EntryEditCustomField entryEditCustomField = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i);
- String key = entryEditCustomField.getLabel();
- if (key == null || key.length() == 0) {
- errorValidation.messageId = R.string.error_string_key;
- return errorValidation;
- }
- }
- }
-
- errorValidation.isValidate = true;
- return errorValidation;
- }
-
- /**
- * Launch a validation with {@link #validate()} and show the error if present
- *
- * @return true if the form was validate or false if not
- */
- protected boolean validateBeforeSaving() {
- ErrorValidation errorValidation = validate();
- errorValidation.showValidationErrorIfNeeded();
- return errorValidation.isValidate;
- }
-
- private void populateEntryWithViewInfo(EntryVersioned newEntry) {
-
- database.startManageEntry(newEntry);
-
- newEntry.setLastAccessTime(new PwDate());
- newEntry.setLastModificationTime(new PwDate());
-
- newEntry.setTitle(entryTitleView.getText().toString());
- newEntry.setIcon(retrieveIcon());
-
- newEntry.setUrl(entryUrlView.getText().toString());
- newEntry.setUsername(entryUserNameView.getText().toString());
- newEntry.setNotes(entryCommentView.getText().toString());
- newEntry.setPassword(entryPasswordView.getText().toString());
-
- if (newEntry.allowExtraFields()) {
- // Delete all extra strings
- newEntry.removeAllCustomFields();
- // Add extra fields from views
- for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
- EntryEditCustomField view = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i);
- String key = view.getLabel();
- String value = view.getValue();
- boolean protect = view.isProtected();
- newEntry.addExtraField(key, new ProtectedString(protect, value));
- }
- }
-
- database.stopManageEntry(newEntry);
- }
-
- /**
- * Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
- */
- private PwIcon retrieveIcon() {
-
- if (!mSelectedIconStandard.isUnknown())
- return mSelectedIconStandard;
- else {
- if (mIsNew) {
- return database.getIconFactory().getKeyIcon();
- }
- else {
- // Keep previous icon, if no new one was selected
- return mEntry.getIcon();
- }
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.database_lock, menu);
- MenuUtil.INSTANCE.contributionMenuInflater(inflater, menu);
-
- return true;
- }
-
- public boolean onOptionsItemSelected(MenuItem item) {
- switch ( item.getItemId() ) {
- case R.id.menu_lock:
- lockAndExit();
- return true;
-
- case R.id.menu_contribute:
- return MenuUtil.INSTANCE.onContributionItemSelected(this);
-
- case android.R.id.home:
- finish();
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- private void assignIconView() {
- database.getDrawFactory()
- .assignDatabaseIconTo(
- this,
- entryIconView,
- mEntry.getIcon(),
- iconColor);
- }
-
- protected void fillData() {
-
- assignIconView();
-
- // Don't start the field reference manager, we want to see the raw ref
- App.Companion.getCurrentDatabase().stopManageEntry(mEntry);
-
- entryTitleView.setText(mEntry.getTitle());
- entryUserNameView.setText(mEntry.getUsername());
- entryUrlView.setText(mEntry.getUrl());
- String password = mEntry.getPassword();
- entryPasswordView.setText(password);
- entryConfirmationPasswordView.setText(password);
- entryCommentView.setText(mEntry.getNotes());
-
- boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
- if (visibilityFontActivated) {
- Util.applyFontVisibilityTo(this, entryUserNameView);
- Util.applyFontVisibilityTo(this, entryPasswordView);
- Util.applyFontVisibilityTo(this, entryConfirmationPasswordView);
- Util.applyFontVisibilityTo(this, entryCommentView);
- }
-
- if (mEntry.allowExtraFields()) {
- LinearLayout container = findViewById(R.id.entry_edit_advanced_container);
- mEntry.getFields().doActionToAllCustomProtectedField((key, value) -> {
- EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this);
- entryEditCustomField.setData(key, value);
- entryEditCustomField.setFontVisibility(visibilityFontActivated);
- container.addView(entryEditCustomField);
- return null;
- });
- }
- }
-
- @Override
- public void iconPicked(Bundle bundle) {
- mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD);
- mEntry.setIcon(mSelectedIconStandard);
- assignIconView();
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- if (!mSelectedIconStandard.isUnknown()) {
- outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard);
- super.onSaveInstanceState(outState);
- }
- }
-
- @Override
- public void acceptPassword(Bundle bundle) {
- String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID);
- entryPasswordView.setText(generatedPassword);
- entryConfirmationPasswordView.setText(generatedPassword);
-
- new Handler().post(() -> performedNextEducation(entryEditActivityEducation));
- }
-
- @Override
- public void cancelPassword(Bundle bundle) {
- // Do nothing here
- }
-
- @Override
- public void finish() {
- // Assign entry callback as a result in all case
- try {
- if (mNewEntry != null) {
- Bundle bundle = new Bundle();
- Intent intentEntry = new Intent();
- bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry);
- intentEntry.putExtras(bundle);
- if (mIsNew) {
- setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
- } else {
- setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
- }
- }
- super.finish();
- } catch (Exception e) {
- // Exception when parcelable can't be done
- Log.e(TAG, "Cant add entry as result", e);
- }
- }
-}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
new file mode 100644
index 000000000..4797af08b
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt
@@ -0,0 +1,542 @@
+/*
+ * Copyright 2019 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 .
+ */
+package com.kunzisoft.keepass.activities
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.os.Handler
+import android.support.v7.widget.Toolbar
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.ScrollView
+import android.widget.Toast
+
+import com.kunzisoft.keepass.R
+import com.kunzisoft.keepass.activities.lock.LockingHideActivity
+import com.kunzisoft.keepass.app.App
+import com.kunzisoft.keepass.database.action.node.ActionNodeValues
+import com.kunzisoft.keepass.database.action.node.AddEntryRunnable
+import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable
+import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
+import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.element.EntryVersioned
+import com.kunzisoft.keepass.database.element.GroupVersioned
+import com.kunzisoft.keepass.database.element.PwDate
+import com.kunzisoft.keepass.database.element.PwIcon
+import com.kunzisoft.keepass.database.element.PwIconStandard
+import com.kunzisoft.keepass.database.element.PwNodeId
+import com.kunzisoft.keepass.database.element.security.ProtectedString
+import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment
+import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment
+import com.kunzisoft.keepass.education.EntryEditActivityEducation
+import com.kunzisoft.keepass.settings.PreferencesUtil
+import com.kunzisoft.keepass.tasks.ActionRunnable
+import com.kunzisoft.keepass.timeout.TimeoutHelper
+import com.kunzisoft.keepass.utils.MenuUtil
+import com.kunzisoft.keepass.utils.Util
+import com.kunzisoft.keepass.view.EntryEditCustomField
+
+import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD
+
+class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener {
+
+ private var mDatabase: Database? = null
+
+ private var mEntry: EntryVersioned? = null
+ private var mParent: GroupVersioned? = null
+ private var mNewEntry: EntryVersioned? = null
+ private var mIsNew: Boolean = false
+ private var mSelectedIconStandard: PwIconStandard? = null
+
+ // Views
+ private var scrollView: ScrollView? = null
+ private var entryTitleView: EditText? = null
+ private var entryIconView: ImageView? = null
+ private var entryUserNameView: EditText? = null
+ private var entryUrlView: EditText? = null
+ private var entryPasswordView: EditText? = null
+ private var entryConfirmationPasswordView: EditText? = null
+ private var generatePasswordView: View? = null
+ private var entryCommentView: EditText? = null
+ private var entryExtraFieldsContainer: ViewGroup? = null
+ private var addNewFieldView: View? = null
+ private var saveView: View? = null
+ private var iconColor: Int = 0
+
+ // View validation message
+ private var validationErrorMessageId = UNKNOWN_MESSAGE
+
+ // Education
+ private var entryEditActivityEducation: EntryEditActivityEducation? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.entry_edit)
+
+ val toolbar = findViewById(R.id.toolbar)
+ setSupportActionBar(toolbar)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+
+ scrollView = findViewById(R.id.entry_edit_scroll)
+ scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
+
+ entryTitleView = findViewById(R.id.entry_edit_title)
+ entryIconView = findViewById(R.id.entry_edit_icon_button)
+ entryUserNameView = findViewById(R.id.entry_edit_user_name)
+ entryUrlView = findViewById(R.id.entry_edit_url)
+ entryPasswordView = findViewById(R.id.entry_edit_password)
+ entryConfirmationPasswordView = findViewById(R.id.entry_edit_confirmation_password)
+ entryCommentView = findViewById(R.id.entry_edit_notes)
+ entryExtraFieldsContainer = findViewById(R.id.entry_edit_advanced_container)
+
+ // Focus view to reinitialize timeout
+ resetAppTimeoutWhenViewFocusedOrChanged(
+ entryTitleView,
+ entryIconView,
+ entryUserNameView,
+ entryUrlView,
+ entryPasswordView,
+ entryConfirmationPasswordView,
+ entryCommentView,
+ entryExtraFieldsContainer)
+
+ // Likely the app has been killed exit the activity
+ mDatabase = App.currentDatabase
+
+ // Retrieve the textColor to tint the icon
+ iconColor = theme
+ .obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary))
+ .getColor(0, Color.WHITE)
+
+ mSelectedIconStandard = mDatabase?.iconFactory?.unknownIcon
+
+ // Entry is retrieve, it's an entry to update
+ intent.getParcelableExtra>(KEY_ENTRY)?.let {
+ mIsNew = false
+ mEntry = mDatabase?.getEntryById(it)
+ mEntry?.let { entry ->
+ mParent = entry.parent
+ fillEntryDataInContentsView(entry)
+ }
+ }
+
+ // Parent is retrieve, it's a new entry to create
+ intent.getParcelableExtra>(KEY_PARENT)?.let {
+ mIsNew = true
+ mEntry = mDatabase?.createEntry()
+ mParent = mDatabase?.getGroupById(it)
+ // Add the default icon
+ mDatabase?.drawFactory?.assignDefaultDatabaseIconTo(this, entryIconView, iconColor)
+ }
+
+ // Close the activity if entry or parent can't be retrieve
+ if (mEntry == null || mParent == null) {
+ finish()
+ return
+ }
+
+ // Assign title
+ title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry)
+
+ // Retrieve the icon after an orientation change
+ savedInstanceState?.let {
+ if (it.containsKey(KEY_ICON_STANDARD)) {
+ iconPicked(it)
+ }
+ }
+
+ // Add listener to the icon
+ entryIconView?.setOnClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
+
+ // Generate password button
+ generatePasswordView = findViewById(R.id.entry_edit_generate_button)
+ generatePasswordView?.setOnClickListener { openPasswordGenerator() }
+
+ // Save button
+ saveView = findViewById(R.id.entry_edit_save)
+ mEntry?.let { entry ->
+ saveView?.setOnClickListener { saveEntry(entry) }
+ }
+
+ if (mEntry?.allowExtraFields() == true) {
+ addNewFieldView = findViewById(R.id.entry_edit_add_new_field)
+ addNewFieldView?.apply {
+ visibility = View.VISIBLE
+ setOnClickListener { addNewCustomField() }
+ }
+ }
+
+ // Verify the education views
+ entryEditActivityEducation = EntryEditActivityEducation(this)
+ entryEditActivityEducation?.let {
+ Handler().post { performedNextEducation(it) }
+ }
+ }
+
+ private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
+ if (generatePasswordView != null
+ && entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
+ generatePasswordView!!,
+ {
+ openPasswordGenerator()
+ },
+ {
+ performedNextEducation(entryEditActivityEducation)
+ }
+ ))
+ else if (mEntry != null
+ && mEntry!!.allowExtraFields()
+ && !mEntry!!.containsCustomFields()
+ && addNewFieldView != null
+ && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
+ addNewFieldView!!,
+ {
+ addNewCustomField()
+ }))
+ ;
+ }
+
+ /**
+ * Open the password generator fragment
+ */
+ private fun openPasswordGenerator() {
+ GeneratePasswordDialogFragment().show(supportFragmentManager, "PasswordGeneratorFragment")
+ }
+
+ /**
+ * Add a new view to fill in the information of the customized field
+ */
+ private fun addNewCustomField() {
+ val entryEditCustomField = EntryEditCustomField(this@EntryEditActivity)
+ entryEditCustomField.setData("", ProtectedString(false, ""))
+ val visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this)
+ entryEditCustomField.setFontVisibility(visibilityFontActivated)
+ entryExtraFieldsContainer?.addView(entryEditCustomField)
+
+ // Scroll bottom
+ scrollView?.post { scrollView?.fullScroll(ScrollView.FOCUS_DOWN) }
+ }
+
+ /**
+ * Saves the new entry or update an existing entry in the database
+ */
+ private fun saveEntry(entry: EntryVersioned) {
+
+ // Launch a validation and show the error if present
+ if (!isValid()) {
+ if (validationErrorMessageId != UNKNOWN_MESSAGE)
+ Toast.makeText(this@EntryEditActivity, validationErrorMessageId, Toast.LENGTH_LONG).show()
+ return
+ }
+ // Clone the entry
+ mDatabase?.let { database ->
+ mNewEntry = EntryVersioned(entry)
+ mNewEntry?.let { newEntry ->
+ populateEntryWithViewInfo(newEntry)
+
+ // Open a progress dialog and save entry
+ var task: ActionRunnable? = null
+ val afterActionNodeFinishRunnable = object : AfterActionNodeFinishRunnable() {
+ override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
+ if (actionNodeValues.success)
+ finish()
+ }
+ }
+ if (mIsNew) {
+ mParent?.let { parent ->
+ task = AddEntryRunnable(this@EntryEditActivity,
+ database,
+ newEntry,
+ parent,
+ afterActionNodeFinishRunnable,
+ !readOnly)
+ }
+
+ } else {
+ task = UpdateEntryRunnable(this@EntryEditActivity,
+ database,
+ entry,
+ newEntry,
+ afterActionNodeFinishRunnable,
+ !readOnly)
+ }
+ Thread(task).start()
+ }
+ }
+ }
+
+ /**
+ * Validate or not the entry form
+ *
+ * @return ErrorValidation An error with a message or a validation without message
+ */
+ private fun isValid(): Boolean {
+
+ // Require title
+ if (entryTitleView?.text.toString().isEmpty()) {
+ validationErrorMessageId = R.string.error_title_required
+ return false
+ }
+
+ // Validate password
+ if (entryPasswordView?.text.toString() != entryConfirmationPasswordView?.text.toString()) {
+ validationErrorMessageId = R.string.error_pass_match
+ return false
+ }
+
+ // Validate extra fields
+ if (mEntry?.allowExtraFields() == true) {
+ entryExtraFieldsContainer?.let {
+ for (i in 0 until it.childCount) {
+ val entryEditCustomField = it.getChildAt(i) as EntryEditCustomField
+ val key = entryEditCustomField.label
+ if (key == null || key.isEmpty()) {
+ validationErrorMessageId = R.string.error_string_key
+ return false
+ }
+ }
+ }
+ }
+ return true
+ }
+
+ private fun populateEntryWithViewInfo(newEntry: EntryVersioned) {
+
+ mDatabase?.startManageEntry(newEntry)
+
+ newEntry.lastAccessTime = PwDate()
+ newEntry.lastModificationTime = PwDate()
+
+ newEntry.title = entryTitleView?.text.toString()
+ newEntry.icon = retrieveIcon()
+
+ newEntry.url = entryUrlView?.text.toString()
+ newEntry.username = entryUserNameView?.text.toString()
+ newEntry.notes = entryCommentView?.text.toString()
+ newEntry.password = entryPasswordView?.text.toString()
+
+ if (newEntry.allowExtraFields()) {
+ // Delete all extra strings
+ newEntry.removeAllCustomFields()
+ // Add extra fields from views
+ entryExtraFieldsContainer?.let {
+ for (i in 0 until it.childCount) {
+ val view = it.getChildAt(i) as EntryEditCustomField
+ val key = view.label
+ val value = view.value
+ val protect = view.isProtected
+ newEntry.addExtraField(key, ProtectedString(protect, value))
+ }
+ }
+ }
+
+ mDatabase?.stopManageEntry(newEntry)
+ }
+
+ /**
+ * Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
+ */
+ private fun retrieveIcon(): PwIcon {
+
+ return if (mSelectedIconStandard?.isUnknown != true)
+ mSelectedIconStandard
+ else {
+ if (mIsNew) {
+ mDatabase?.iconFactory?.keyIcon
+ } else {
+ // Keep previous icon, if no new one was selected
+ mEntry?.icon
+ }
+ } ?: PwIconStandard()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ super.onCreateOptionsMenu(menu)
+
+ val inflater = menuInflater
+ inflater.inflate(R.menu.database_lock, menu)
+ MenuUtil.contributionMenuInflater(inflater, menu)
+
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.menu_lock -> {
+ lockAndExit()
+ return true
+ }
+
+ R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
+
+ android.R.id.home -> finish()
+ }
+
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun assignIconView() {
+ mEntry?.icon?.let {
+ mDatabase?.drawFactory?.assignDatabaseIconTo(
+ this,
+ entryIconView,
+ it,
+ iconColor)
+ }
+ }
+
+ private fun fillEntryDataInContentsView(entry: EntryVersioned) {
+
+ assignIconView()
+
+ // Don't start the field reference manager, we want to see the raw ref
+ mDatabase?.stopManageEntry(entry)
+
+ entryTitleView?.setText(entry.title)
+ entryUserNameView?.setText(entry.username)
+ entryUrlView?.setText(entry.url)
+ val password = entry.password
+ entryPasswordView?.setText(password)
+ entryConfirmationPasswordView?.setText(password)
+ entryCommentView?.setText(entry.notes)
+
+ val visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this)
+ if (visibilityFontActivated) {
+ Util.applyFontVisibilityTo(this, entryUserNameView)
+ Util.applyFontVisibilityTo(this, entryPasswordView)
+ Util.applyFontVisibilityTo(this, entryConfirmationPasswordView)
+ Util.applyFontVisibilityTo(this, entryCommentView)
+ }
+
+ if (entry.allowExtraFields()) {
+ val container = findViewById(R.id.entry_edit_advanced_container)
+ entry.fields.doActionToAllCustomProtectedField { key, value ->
+ val entryEditCustomField = EntryEditCustomField(this@EntryEditActivity)
+ entryEditCustomField.setData(key, value)
+ entryEditCustomField.setFontVisibility(visibilityFontActivated)
+ container.addView(entryEditCustomField)
+ }
+ }
+ }
+
+ override fun iconPicked(bundle: Bundle) {
+ mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD)
+ mSelectedIconStandard?.let {
+ mEntry?.icon = it
+ }
+ assignIconView()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ if (!mSelectedIconStandard!!.isUnknown) {
+ outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard)
+ super.onSaveInstanceState(outState)
+ }
+ }
+
+ override fun acceptPassword(bundle: Bundle) {
+ bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let {
+ entryPasswordView?.setText(it)
+ entryConfirmationPasswordView?.setText(it)
+ }
+
+ entryEditActivityEducation?.let {
+ Handler().post { performedNextEducation(it) }
+ }
+ }
+
+ override fun cancelPassword(bundle: Bundle) {
+ // Do nothing here
+ }
+
+ override fun finish() {
+ // Assign entry callback as a result in all case
+ try {
+ mNewEntry?.let {
+ val bundle = Bundle()
+ val intentEntry = Intent()
+ bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry)
+ intentEntry.putExtras(bundle)
+ if (mIsNew) {
+ setResult(ADD_ENTRY_RESULT_CODE, intentEntry)
+ } else {
+ setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry)
+ }
+ }
+ super.finish()
+ } catch (e: Exception) {
+ // Exception when parcelable can't be done
+ Log.e(TAG, "Cant add entry as result", e)
+ }
+ }
+
+ companion object {
+
+ private val TAG = EntryEditActivity::class.java.name
+
+ // Keys for current Activity
+ const val KEY_ENTRY = "entry"
+ const val KEY_PARENT = "parent"
+
+ // Keys for callback
+ const val ADD_ENTRY_RESULT_CODE = 31
+ const val UPDATE_ENTRY_RESULT_CODE = 32
+ const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129
+ const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"
+
+ const val UNKNOWN_MESSAGE = -1
+
+ /**
+ * Launch EntryEditActivity to update an existing entry
+ *
+ * @param activity from activity
+ * @param pwEntry Entry to update
+ */
+ fun launch(activity: Activity, pwEntry: EntryVersioned) {
+ if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
+ val intent = Intent(activity, EntryEditActivity::class.java)
+ intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
+ activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
+ }
+ }
+
+ /**
+ * Launch EntryEditActivity to add a new entry
+ *
+ * @param activity from activity
+ * @param pwGroup Group who will contains new entry
+ */
+ fun launch(activity: Activity, pwGroup: GroupVersioned) {
+ if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
+ val intent = Intent(activity, EntryEditActivity::class.java)
+ intent.putExtra(KEY_PARENT, pwGroup.nodeId)
+ activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
index 4a0266b63..931019011 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
@@ -298,7 +298,7 @@ public class GroupActivity extends LockingActivity
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP));
addNodeButtonView.setAddEntryClickListener(v ->
- EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
+ EntryEditActivity.Companion.launch(GroupActivity.this, mCurrentGroup));
// Search suggestion
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
@@ -570,7 +570,7 @@ public class GroupActivity extends LockingActivity
GroupEditDialogFragment.TAG_CREATE_GROUP);
break;
case ENTRY:
- EntryEditActivity.launch(GroupActivity.this, (EntryVersioned) node);
+ EntryEditActivity.Companion.launch(GroupActivity.this, (EntryVersioned) node);
break;
}
return true;
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
index 6849211e3..dfd61bc85 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt
@@ -163,9 +163,9 @@ abstract class LockingActivity : StylishActivity() {
/**
* To reset the app timeout when a view is focused or changed
*/
- protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View) {
+ protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) {
views.forEach {
- it.setOnFocusChangeListener { _, hasFocus ->
+ it?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
}