Kotlinized EntryEditActivity

This commit is contained in:
J-Jamet
2019-07-07 18:48:56 +02:00
parent cc35a1e8aa
commit 191e0cc654
4 changed files with 546 additions and 571 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>.
*
*/
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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Toolbar>(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<PwNodeId<*>>(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<PwNodeId<*>>(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<LinearLayout>(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)
}
}
}
}

View File

@@ -298,7 +298,7 @@ public class GroupActivity extends LockingActivity
.show(getSupportFragmentManager(), .show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP)); GroupEditDialogFragment.TAG_CREATE_GROUP));
addNodeButtonView.setAddEntryClickListener(v -> addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup)); EntryEditActivity.Companion.launch(GroupActivity.this, mCurrentGroup));
// Search suggestion // Search suggestion
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database); searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
@@ -570,7 +570,7 @@ public class GroupActivity extends LockingActivity
GroupEditDialogFragment.TAG_CREATE_GROUP); GroupEditDialogFragment.TAG_CREATE_GROUP);
break; break;
case ENTRY: case ENTRY:
EntryEditActivity.launch(GroupActivity.this, (EntryVersioned) node); EntryEditActivity.Companion.launch(GroupActivity.this, (EntryVersioned) node);
break; break;
} }
return true; return true;

View File

@@ -163,9 +163,9 @@ abstract class LockingActivity : StylishActivity() {
/** /**
* To reset the app timeout when a view is focused or changed * 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 { views.forEach {
it.setOnFocusChangeListener { _, hasFocus -> it?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) { if (hasFocus) {
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
} }