Add a new activity that allows editing key profiles

This commit is contained in:
Alexander Bakker 2017-12-27 22:04:22 +01:00
parent 05cfc0bc5f
commit 07c3e43160
15 changed files with 551 additions and 13 deletions

View file

@ -0,0 +1,220 @@
package me.impy.aegis;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import me.impy.aegis.crypto.KeyInfo;
import me.impy.aegis.db.DatabaseEntry;
import me.impy.aegis.encoding.Base32;
import me.impy.aegis.helpers.SpinnerHelper;
public class EditProfileActivity extends AegisActivity {
private boolean _edited = false;
private KeyProfile _profile;
private EditText _textName;
private EditText _textIssuer;
private EditText _textPeriod;
private EditText _textSecret;
private Spinner _spinnerType;
private Spinner _spinnerAlgo;
private Spinner _spinnerDigits;
private SpinnerItemSelectedListener _selectedListener = new SpinnerItemSelectedListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_profile);
_profile = (KeyProfile) getIntent().getSerializableExtra("KeyProfile");
ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
ImageView imageView = findViewById(R.id.profile_drawable);
imageView.setImageDrawable(_profile.getDrawable());
DatabaseEntry entry = _profile.getEntry();
_textName = findViewById(R.id.text_name);
_textName.setText(entry.getName());
_textName.addTextChangedListener(watcher);
_textIssuer = findViewById(R.id.text_issuer);
_textIssuer.setText(entry.getInfo().getIssuer());
_textIssuer.addTextChangedListener(watcher);
_textPeriod = findViewById(R.id.text_period);
_textPeriod.setText(Integer.toString(entry.getInfo().getPeriod()));
_textPeriod.addTextChangedListener(watcher);
_textSecret = findViewById(R.id.text_secret);
_textSecret.setText(Base32.encodeOriginal(entry.getInfo().getSecret()));
_textSecret.addTextChangedListener(watcher);
_spinnerType = findViewById(R.id.spinner_type);
SpinnerHelper.fillSpinner(this, _spinnerType, R.array.otp_types_array);
_spinnerType.setOnTouchListener(_selectedListener);
_spinnerType.setOnItemSelectedListener(_selectedListener);
_spinnerAlgo = findViewById(R.id.spinner_algo);
SpinnerHelper.fillSpinner(this, _spinnerAlgo, R.array.otp_algo_array);
_spinnerAlgo.setOnTouchListener(_selectedListener);
_spinnerAlgo.setOnItemSelectedListener(_selectedListener);
_spinnerDigits = findViewById(R.id.spinner_digits);
SpinnerHelper.fillSpinner(this, _spinnerDigits, R.array.otp_digits_array);
_spinnerDigits.setOnTouchListener(_selectedListener);
_spinnerDigits.setOnItemSelectedListener(_selectedListener);
}
@Override
protected void setPreferredTheme(boolean nightMode) {
if (nightMode) {
setTheme(R.style.AppTheme_Dark_TransparentActionBar);
} else {
setTheme(R.style.AppTheme_Default_TransparentActionBar);
}
}
@Override
public void onBackPressed() {
if (!_edited) {
super.onBackPressed();
return;
}
new AlertDialog.Builder(this)
.setMessage("Your changes have not been saved")
.setPositiveButton(R.string.save, (dialog, which) -> onSave())
.setNegativeButton(R.string.discard, (dialog, which) -> super.onBackPressed())
.show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.action_save:
return onSave();
case R.id.action_delete:
return onDelete();
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_edit, menu);
return true;
}
private boolean onDelete() {
return false;
}
private boolean onSave() {
int period;
try {
period = Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) {
onError("Period is not an integer.");
return false;
}
String type = _spinnerType.getSelectedItem().toString();
String algo = _spinnerAlgo.getSelectedItem().toString();
int digits;
try {
digits = Integer.parseInt(_spinnerDigits.getSelectedItem().toString());
} catch (NumberFormatException e) {
onError("Digits is not an integer.");
return false;
}
DatabaseEntry entry = _profile.getEntry();
entry.setName(_textName.getText().toString());
KeyInfo info = entry.getInfo();
info.setIssuer(_textIssuer.getText().toString());
info.setSecret(Base32.decode(_textSecret.getText().toString()));
info.setPeriod(period);
info.setDigits(digits);
info.setAlgorithm(algo);
info.setType(type);
Intent intent = new Intent();
intent.putExtra("KeyProfile", _profile);
setResult(RESULT_OK, intent);
finish();
return true;
}
private void onError(String msg) {
new AlertDialog.Builder(this)
.setTitle("Error saving profile")
.setMessage(msg)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void onFieldEdited() {
_edited = true;
}
private TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
onFieldEdited();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
onFieldEdited();
}
@Override
public void afterTextChanged(Editable s) {
onFieldEdited();
}
};
private class SpinnerItemSelectedListener implements AdapterView.OnItemSelectedListener, View.OnTouchListener {
private boolean _userSelect = false;
@Override
public boolean onTouch(View v, MotionEvent event) {
_userSelect = true;
return false;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (_userSelect) {
onFieldEdited();
_userSelect = false;
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
}

View file

@ -1,5 +1,8 @@
package me.impy.aegis;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import java.io.Serializable;
import java.lang.reflect.UndeclaredThrowableException;
@ -29,4 +32,16 @@ public class KeyProfile implements Serializable {
}
return _code;
}
public TextDrawable getDrawable() {
String name = _entry.getName();
if (name == null) {
return null;
}
ColorGenerator generator = ColorGenerator.MATERIAL;
int color = generator.getColor(name);
return TextDrawable.builder().buildRound(name.substring(0, 1).toUpperCase(), color);
}
}

View file

@ -46,6 +46,18 @@ public class KeyProfileAdapter extends RecyclerView.Adapter<KeyProfileHolder> im
notifyDataSetChanged();
}
public void replaceKey(KeyProfile newProfile) {
for (KeyProfile oldProfile : _keyProfiles) {
if (oldProfile.getEntry().getID() == newProfile.getEntry().getID()) {
int position = _keyProfiles.indexOf(oldProfile);
_keyProfiles.set(position, newProfile);
notifyItemChanged(position);
return;
}
}
throw new AssertionError("no key profile found with the same id");
}
@Override
public void onItemDismiss(int position) {

View file

@ -46,7 +46,8 @@ public class KeyProfileHolder extends RecyclerView.ViewHolder {
_profileIssuer.setText(" - " + profile.getEntry().getInfo().getIssuer());
}
_profileDrawable.setImageDrawable(generateTextDrawable(profile));
TextDrawable drawable = profile.getDrawable();
_profileDrawable.setImageDrawable(drawable);
}
public void startUpdateLoop() {
@ -88,15 +89,4 @@ public class KeyProfileHolder extends RecyclerView.ViewHolder {
animation.start();
return true;
}
private TextDrawable generateTextDrawable(KeyProfile profile) {
if (_profileName == null) {
return null;
}
ColorGenerator generator = ColorGenerator.MATERIAL;
int profileKeyColor = generator.getColor(profile.getEntry().getName());
return TextDrawable.builder().buildRound(profile.getEntry().getName().substring(0, 1).toUpperCase(), profileKeyColor);
}
}

View file

@ -79,6 +79,10 @@ public class KeyProfileView extends Fragment implements KeyProfileAdapter.Listen
_adapter.clearKeys();
}
public void replaceKey(KeyProfile profile) {
_adapter.replaceKey(profile);
}
public interface Listener {
void onEntryClick(KeyProfile profile);
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);

View file

@ -139,6 +139,9 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
case CODE_ADD_KEYINFO:
onAddKeyInfoResult(resultCode, data);
break;
case CODE_EDIT_KEYINFO:
onEditKeyInfoResult(resultCode, data);
break;
case CODE_DO_INTRO:
onDoIntroResult(resultCode, data);
break;
@ -319,6 +322,23 @@ public class MainActivity extends AegisActivity implements KeyProfileView.Listen
}
}
private void onEditKeyInfoResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
// this profile has been serialized/deserialized and is no longer the same instance it once was
// to deal with this, the replaceKey functions are used
KeyProfile profile = (KeyProfile) data.getSerializableExtra("KeyProfile");
try {
_db.replaceKey(profile.getEntry());
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "An error occurred while trying to update an entry", Toast.LENGTH_SHORT).show();
return;
}
_keyProfileView.replaceKey(profile);
saveDatabase();
}
}
private void addKey(KeyProfile profile) {
profile.refreshCode();

View file

@ -67,6 +67,16 @@ public class Database {
_entries.remove(entry);
}
public void replaceKey(DatabaseEntry newEntry) {
for (DatabaseEntry oldEntry : _entries) {
if (oldEntry.getID() == newEntry.getID()) {
_entries.set(_entries.indexOf(oldEntry), newEntry);
return;
}
}
throw new AssertionError("no entry found with the same id");
}
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) {
Collections.swap(_entries, _entries.indexOf(entry1), _entries.indexOf(entry2));
}

View file

@ -152,6 +152,11 @@ public class DatabaseManager {
_db.removeKey(entry);
}
public void replaceKey(DatabaseEntry entry) throws Exception {
assertState(false, true);
_db.replaceKey(entry);
}
public void swapKeys(DatabaseEntry entry1, DatabaseEntry entry2) throws Exception {
assertState(false, true);
_db.swapKeys(entry1, entry2);

View file

@ -0,0 +1,19 @@
package me.impy.aegis.helpers;
import android.content.Context;
import android.support.annotation.ArrayRes;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
public class SpinnerHelper {
private SpinnerHelper() {
}
public static void fillSpinner(Context context, Spinner spinner, @ArrayRes int textArrayResId) {
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context, textArrayResId, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.invalidate();
}
}