mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-23 15:29:15 +00:00
Merge branch 'master' of https://github.com/alexbakker/Aegis
This commit is contained in:
commit
5000522381
21 changed files with 278 additions and 76 deletions
|
@ -32,10 +32,13 @@ dependencies {
|
|||
implementation 'com.android.support:recyclerview-v7:27.1.1'
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.android.support:design:27.1.1'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.takisoft.fix:preference-v7:27.1.1.1'
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9'
|
||||
implementation 'com.android.support:cardview-v7:27.1.1'
|
||||
implementation 'com.github.esafirm.android-image-picker:imagepicker:1.13.0'
|
||||
implementation 'com.github.avito-tech:krop:3e65e12'
|
||||
implementation 'com.android.support:support-v4:27.1.1'
|
||||
implementation 'com.mattprecious.swirl:swirl:1.0.0'
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import me.impy.aegis.encoding.Base64Exception;
|
||||
import me.impy.aegis.otp.OtpInfoException;
|
||||
|
||||
public class Database {
|
||||
|
@ -43,7 +44,7 @@ public class Database {
|
|||
entry.deserialize(array.getJSONObject(i));
|
||||
addEntry(entry);
|
||||
}
|
||||
} catch (OtpInfoException | JSONException e) {
|
||||
} catch (Base64Exception | OtpInfoException | JSONException e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import org.json.JSONObject;
|
|||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import me.impy.aegis.encoding.Base64;
|
||||
import me.impy.aegis.encoding.Base64Exception;
|
||||
import me.impy.aegis.otp.OtpInfo;
|
||||
import me.impy.aegis.otp.OtpInfoException;
|
||||
|
||||
|
@ -13,8 +15,8 @@ public class DatabaseEntry implements Serializable {
|
|||
private UUID _uuid;
|
||||
private String _name = "";
|
||||
private String _issuer = "";
|
||||
private String _icon = "";
|
||||
private OtpInfo _info;
|
||||
private byte[] _icon;
|
||||
|
||||
public DatabaseEntry(OtpInfo info) {
|
||||
_info = info;
|
||||
|
@ -35,6 +37,7 @@ public class DatabaseEntry implements Serializable {
|
|||
obj.put("uuid", _uuid.toString());
|
||||
obj.put("name", _name);
|
||||
obj.put("issuer", _issuer);
|
||||
obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon));
|
||||
obj.put("info", _info.toJson());
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -43,7 +46,7 @@ public class DatabaseEntry implements Serializable {
|
|||
return obj;
|
||||
}
|
||||
|
||||
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException {
|
||||
public void deserialize(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
|
||||
// if there is no uuid, generate a new one
|
||||
if (!obj.has("uuid")) {
|
||||
_uuid = UUID.randomUUID();
|
||||
|
@ -52,6 +55,12 @@ public class DatabaseEntry implements Serializable {
|
|||
}
|
||||
_name = obj.getString("name");
|
||||
_issuer = obj.getString("issuer");
|
||||
|
||||
Object icon = obj.get("icon");
|
||||
if (icon != JSONObject.NULL) {
|
||||
_icon = Base64.decode((String) icon);
|
||||
}
|
||||
|
||||
_info = OtpInfo.parseJson(obj.getString("type"), obj.getJSONObject("info"));
|
||||
}
|
||||
|
||||
|
@ -67,7 +76,7 @@ public class DatabaseEntry implements Serializable {
|
|||
return _issuer;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
public byte[] getIcon() {
|
||||
return _icon;
|
||||
}
|
||||
|
||||
|
@ -83,11 +92,15 @@ public class DatabaseEntry implements Serializable {
|
|||
_issuer = issuer;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
public void setInfo(OtpInfo info) {
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public void setIcon(byte[] icon) {
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
public boolean hasIcon() {
|
||||
return _icon != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package me.impy.aegis.helpers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator;
|
||||
|
||||
|
@ -8,7 +10,7 @@ public class TextDrawableHelper {
|
|||
|
||||
}
|
||||
|
||||
public static TextDrawable generate(String text, String fallback) {
|
||||
public static TextDrawable generate(String text, String fallback, View view) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
if (fallback == null || fallback.isEmpty()) {
|
||||
return null;
|
||||
|
@ -16,8 +18,11 @@ public class TextDrawableHelper {
|
|||
text = fallback;
|
||||
}
|
||||
|
||||
ColorGenerator generator = ColorGenerator.MATERIAL;
|
||||
int color = generator.getColor(text);
|
||||
return TextDrawable.builder().buildRound(text.substring(0, 1).toUpperCase(), color);
|
||||
int color = ColorGenerator.MATERIAL.getColor(text);
|
||||
return TextDrawable.builder().beginConfig()
|
||||
.width(view.getLayoutParams().width)
|
||||
.height(view.getLayoutParams().height)
|
||||
.endConfig()
|
||||
.buildRect(text.substring(0, 1).toUpperCase(), color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package me.impy.aegis.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.ArrayRes;
|
||||
import android.support.v7.app.ActionBar;
|
||||
|
@ -22,7 +28,14 @@ import android.widget.Spinner;
|
|||
import android.widget.TableRow;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.avito.android.krop.KropView;
|
||||
import com.esafirm.imagepicker.features.ImagePicker;
|
||||
import com.esafirm.imagepicker.features.ReturnMode;
|
||||
import com.esafirm.imagepicker.model.Image;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import me.impy.aegis.R;
|
||||
import me.impy.aegis.db.DatabaseEntry;
|
||||
import me.impy.aegis.encoding.Base32;
|
||||
|
@ -40,8 +53,9 @@ public class EditEntryActivity extends AegisActivity {
|
|||
private boolean _isNew = false;
|
||||
private boolean _edited = false;
|
||||
private DatabaseEntry _entry;
|
||||
|
||||
private ImageView _iconView;
|
||||
private boolean _hasCustomImage = false;
|
||||
private CircleImageView _iconView;
|
||||
private ImageView _saveImageButton;
|
||||
|
||||
private EditText _textName;
|
||||
private EditText _textIssuer;
|
||||
|
@ -57,6 +71,8 @@ public class EditEntryActivity extends AegisActivity {
|
|||
private Spinner _spinnerDigits;
|
||||
private SpinnerItemSelectedListener _selectedListener = new SpinnerItemSelectedListener();
|
||||
|
||||
private KropView _kropView;
|
||||
|
||||
private RelativeLayout _advancedSettingsHeader;
|
||||
private RelativeLayout _advancedSettings;
|
||||
|
||||
|
@ -79,6 +95,8 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
// set up fields
|
||||
_iconView = findViewById(R.id.profile_drawable);
|
||||
_kropView = findViewById(R.id.krop_view);
|
||||
_saveImageButton = findViewById(R.id.iv_saveImage);
|
||||
_textName = findViewById(R.id.text_name);
|
||||
_textIssuer = findViewById(R.id.text_issuer);
|
||||
_textPeriod = findViewById(R.id.text_period);
|
||||
|
@ -97,8 +115,15 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
// fill the fields with values if possible
|
||||
if (_entry != null) {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName());
|
||||
_iconView.setImageDrawable(drawable);
|
||||
if (_entry.hasIcon()) {
|
||||
byte[] imageBytes = _entry.getIcon();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||
_iconView.setImageBitmap(bitmap);
|
||||
_hasCustomImage = true;
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
_textName.setText(_entry.getName());
|
||||
_textIssuer.setText(_entry.getIssuer());
|
||||
|
@ -173,6 +198,24 @@ public class EditEntryActivity extends AegisActivity {
|
|||
}
|
||||
});
|
||||
|
||||
ImagePicker imagePicker = ImagePicker.create(this)
|
||||
.returnMode(ReturnMode.ALL) // set whether pick and / or camera action should return immediate result or not.
|
||||
.folderMode(true) // folder mode (false by default)
|
||||
.toolbarFolderTitle("Folder") // folder selection title
|
||||
.toolbarImageTitle("Tap to select") // image selection title
|
||||
.toolbarArrowColor(Color.BLACK) // Toolbar 'up' arrow color
|
||||
.single() // single mode
|
||||
.showCamera(false) // show camera or not (true by default)
|
||||
.imageDirectory("Camera");
|
||||
|
||||
// Open ImagePicker when clicking on the icon
|
||||
_iconView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
imagePicker.start(); // start image picker activity with request code
|
||||
}
|
||||
});
|
||||
|
||||
_advancedSettingsHeader.setOnClickListener(v -> {
|
||||
openAdvancedSettings();
|
||||
});
|
||||
|
@ -265,6 +308,10 @@ public class EditEntryActivity extends AegisActivity {
|
|||
finish(true);
|
||||
});
|
||||
break;
|
||||
case R.id.action_default_image:
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
_hasCustomImage = false;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -278,6 +325,10 @@ public class EditEntryActivity extends AegisActivity {
|
|||
if (_isNew) {
|
||||
menu.findItem(R.id.action_delete).setVisible(false);
|
||||
}
|
||||
if (!_hasCustomImage) {
|
||||
menu.findItem(R.id.action_default_image).setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -289,6 +340,29 @@ public class EditEntryActivity extends AegisActivity {
|
|||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, final int resultCode, Intent data) {
|
||||
if (ImagePicker.shouldHandle(requestCode, resultCode, data)) {
|
||||
// or get a single image only
|
||||
Image image = ImagePicker.getFirstImageOrNull(data);
|
||||
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(image.getPath(),bmOptions);
|
||||
_kropView.setBitmap(bitmap);
|
||||
_kropView.setVisibility(View.VISIBLE);
|
||||
|
||||
_saveImageButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
_iconView.setImageBitmap(_kropView.getCroppedBitmap());
|
||||
_kropView.setVisibility(View.GONE);
|
||||
_hasCustomImage = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
private boolean onSave() {
|
||||
if (_textSecret.length() == 0) {
|
||||
onError("Secret is a required field.");
|
||||
|
@ -359,6 +433,15 @@ public class EditEntryActivity extends AegisActivity {
|
|||
entry.setIssuer(_textIssuer.getText().toString());
|
||||
entry.setName(_textName.getText().toString());
|
||||
|
||||
if (_hasCustomImage) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
drawableToBitmap(_iconView.getDrawable()).compress(Bitmap.CompressFormat.JPEG, 100, stream);
|
||||
byte[] bitmapdata = stream.toByteArray();
|
||||
entry.setIcon(bitmapdata);
|
||||
} else {
|
||||
entry.setIcon(null);
|
||||
}
|
||||
|
||||
_entry = entry;
|
||||
finish(false);
|
||||
return true;
|
||||
|
@ -404,8 +487,10 @@ public class EditEntryActivity extends AegisActivity {
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString());
|
||||
_iconView.setImageDrawable(drawable);
|
||||
if (!_hasCustomImage) {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -441,4 +526,25 @@ public class EditEntryActivity extends AegisActivity {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Bitmap drawableToBitmap(Drawable drawable) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
final int width = !drawable.getBounds().isEmpty() ? drawable
|
||||
.getBounds().width() : drawable.getIntrinsicWidth();
|
||||
|
||||
final int height = !drawable.getBounds().isEmpty() ? drawable
|
||||
.getBounds().height() : drawable.getIntrinsicHeight();
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(width <= 0 ? 1 : width,
|
||||
height <= 0 ? 1 : height, Bitmap.Config.ARGB_8888);
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package me.impy.aegis.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
|
|
@ -327,7 +327,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
updateLockIcon();
|
||||
|
||||
// refresh all codes to prevent showing old ones
|
||||
_entryListView.refresh();
|
||||
_entryListView.refresh(true);
|
||||
}
|
||||
|
||||
private BottomSheetDialog createBottomSheet(final DatabaseEntry entry) {
|
||||
|
@ -411,9 +411,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
private void loadEntries() {
|
||||
updateLockIcon();
|
||||
|
||||
for (DatabaseEntry entry : _db.getEntries()) {
|
||||
_entryListView.addEntry(entry);
|
||||
}
|
||||
_entryListView.addEntries(_db.getEntries());
|
||||
}
|
||||
|
||||
private void updateLockIcon() {
|
||||
|
|
|
@ -221,6 +221,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
|||
}
|
||||
|
||||
private void onImport() {
|
||||
if (!PermissionHelper.request(getActivity(), CODE_PERM_IMPORT, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getImporters();
|
||||
String[] names = importers.keySet().toArray(new String[importers.size()]);
|
||||
|
||||
|
@ -232,11 +236,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat implements Pas
|
|||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
_importerType = importers.get(names[i]);
|
||||
|
||||
if (PermissionHelper.request(getActivity(), CODE_PERM_IMPORT, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, CODE_IMPORT);
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, CODE_IMPORT);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
|
|
|
@ -8,10 +8,8 @@ import android.support.v4.app.Fragment;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.paolorotolo.appintro.ISlidePolicy;
|
||||
|
@ -25,7 +23,6 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
|||
public static final int CRYPT_TYPE_PASS = 2;
|
||||
public static final int CRYPT_TYPE_FINGER = 3;
|
||||
|
||||
private Spinner _authenticationSpinner;
|
||||
private RadioGroup _buttonGroup;
|
||||
private int _bgColor;
|
||||
|
||||
|
@ -36,13 +33,13 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
|||
_buttonGroup.setOnCheckedChangeListener(this);
|
||||
onCheckedChanged(_buttonGroup, _buttonGroup.getCheckedRadioButtonId());
|
||||
|
||||
// only show the fingerprint option if the api version is new enough, permission is granted and a scanner is found
|
||||
// only enable the fingerprint option if the api version is new enough, permission is granted and a scanner is found
|
||||
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
||||
if (manager != null) {
|
||||
RadioButton button = view.findViewById(R.id.rb_fingerprint);
|
||||
TextView text = view.findViewById(R.id.text_rb_fingerprint);
|
||||
button.setEnabled(false);
|
||||
text.setEnabled(false);
|
||||
button.setEnabled(true);
|
||||
text.setEnabled(true);
|
||||
}
|
||||
|
||||
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
|
||||
|
|
|
@ -23,8 +23,12 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
private static Listener _listener;
|
||||
private boolean _showIssuer;
|
||||
|
||||
// keeps track of the viewholders that are currently bound
|
||||
private List<EntryHolder> _holders;
|
||||
|
||||
public EntryAdapter(Listener listener) {
|
||||
_entries = new ArrayList<>();
|
||||
_holders = new ArrayList<>();
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
|
@ -43,6 +47,11 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
}
|
||||
}
|
||||
|
||||
public void addEntries(List<DatabaseEntry> entries) {
|
||||
_entries.addAll(entries);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
entry = getEntryByUUID(entry.getUUID());
|
||||
int position = _entries.indexOf(entry);
|
||||
|
@ -71,6 +80,16 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
throw new AssertionError("no entry found with the same id");
|
||||
}
|
||||
|
||||
public void refresh(boolean hard) {
|
||||
if (hard) {
|
||||
notifyDataSetChanged();
|
||||
} else {
|
||||
for (EntryHolder holder : _holders) {
|
||||
holder.refreshCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDismiss(int position) {
|
||||
|
||||
|
@ -100,7 +119,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
@Override
|
||||
public void onViewRecycled(EntryHolder holder) {
|
||||
holder.stopRefreshLoop();
|
||||
super.onViewRecycled(holder);
|
||||
_holders.remove(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,6 +163,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
holder.refreshCode();
|
||||
}
|
||||
});
|
||||
|
||||
_holders.add(holder);
|
||||
}
|
||||
|
||||
public int getUniformPeriod() {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package me.impy.aegis.ui.views;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
@ -72,8 +76,14 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
_profileIssuer.setText(" - " + entry.getIssuer());
|
||||
}
|
||||
|
||||
TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName());
|
||||
_profileDrawable.setImageDrawable(drawable);
|
||||
if (_entry.hasIcon()) {
|
||||
byte[] imageBytes = entry.getIcon();
|
||||
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
|
||||
_profileDrawable.setImageBitmap(image);
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName(), _profileDrawable);
|
||||
_profileDrawable.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
refreshCode();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.impy.aegis.R;
|
||||
import me.impy.aegis.db.DatabaseEntry;
|
||||
import me.impy.aegis.helpers.SimpleItemTouchHelperCallback;
|
||||
|
@ -51,7 +53,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_refresher = new UiRefresher(new UiRefresher.Listener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
refresh();
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,11 +65,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
return view;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
public void refresh(boolean hard) {
|
||||
if (_showProgress) {
|
||||
_progressBar.refresh();
|
||||
}
|
||||
_adapter.notifyDataSetChanged();
|
||||
_adapter.refresh(hard);
|
||||
}
|
||||
|
||||
private void checkPeriodUniformity() {
|
||||
|
@ -88,11 +90,12 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
}
|
||||
|
||||
private void startRefreshLoop() {
|
||||
refresh(true);
|
||||
_refresher.start();
|
||||
}
|
||||
|
||||
private void stopRefreshLoop() {
|
||||
refresh();
|
||||
refresh(true);
|
||||
_refresher.stop();
|
||||
}
|
||||
|
||||
|
@ -127,7 +130,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
|
||||
public void setShowIssuer(boolean showIssuer) {
|
||||
_adapter.setShowIssuer(showIssuer);
|
||||
_adapter.notifyDataSetChanged();
|
||||
_adapter.refresh(true);
|
||||
}
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
|
@ -135,6 +138,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void addEntries(List<DatabaseEntry> entries) {
|
||||
_adapter.addEntries(entries);
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
_adapter.removeEntry(entry);
|
||||
checkPeriodUniformity();
|
||||
|
|
9
app/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_check_black_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
|
@ -16,18 +16,36 @@
|
|||
android:layout_height="250dp"
|
||||
android:background="@color/colorPrimary">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:paddingTop="100dp"
|
||||
android:background="@color/colorPrimary">
|
||||
<ImageView
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/profile_drawable"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:id="@+id/profile_drawable"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
</RelativeLayout>
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
/>
|
||||
|
||||
<com.avito.android.krop.KropView
|
||||
android:id="@+id/krop_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="invisible"
|
||||
app:krop_aspectX="1"
|
||||
app:krop_aspectY="1"
|
||||
app:krop_offset="70dp"
|
||||
app:krop_overlayColor="#aaffffff" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_saveImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:layout_margin="15dp"
|
||||
android:src="@drawable/ic_check_black_24dp"
|
||||
android:tint="?attr/iconColorPrimary" />
|
||||
</com.avito.android.krop.KropView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -21,12 +21,14 @@
|
|||
android:layout_height="match_parent"
|
||||
android:paddingLeft="16dp">
|
||||
|
||||
<ImageView android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:id="@+id/ivTextDrawable"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"/>
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/ivTextDrawable"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_fingerprint"
|
||||
android:enabled="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_method_fingerprint"
|
||||
|
@ -80,6 +81,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text_rb_fingerprint"
|
||||
android:enabled="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:id="@+id/progressBar"
|
||||
android:visibility="gone"
|
||||
android:max="1000"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
|
|
|
@ -11,4 +11,9 @@
|
|||
android:id="@+id/action_delete"
|
||||
android:title="@string/action_delete"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_default_image"
|
||||
android:title="@string/action_default_image"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<string name="action_settings">Settings</string>
|
||||
<string name="action_import">Import</string>
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="action_default_image">Set default image</string>
|
||||
<string name="discard">Discard</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="title_activity_intro">IntroActivity</string>
|
||||
|
|
25
testdata/aegis_export.json
vendored
25
testdata/aegis_export.json
vendored
File diff suppressed because one or more lines are too long
12
testdata/aegis_export_plain.json
vendored
12
testdata/aegis_export_plain.json
vendored
|
@ -12,6 +12,7 @@
|
|||
"uuid": "8e986927-a7a3-4d35-88fb-509ae7960155",
|
||||
"name": "Bob",
|
||||
"issuer": "Google",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "KD3WHMYQ7K7DGV5QFJNN2ZUATBC44C7F",
|
||||
"algo": "SHA1",
|
||||
|
@ -24,6 +25,7 @@
|
|||
"uuid": "97155de0-fb6a-4dfa-ac59-2abe54f5d059",
|
||||
"name": "Henk",
|
||||
"issuer": "Scaleway",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "MBQX47S3XOSOF7YT2CO3LZNACULZNY4P",
|
||||
"algo": "SHA256",
|
||||
|
@ -36,6 +38,7 @@
|
|||
"uuid": "a6276843-3c95-43b7-a3ef-e824c5534801",
|
||||
"name": "nodejsc0d3r",
|
||||
"issuer": "Github",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "642GK5EROWKUAEBFRH6FWSMM4FRKS5IA",
|
||||
"algo": "SHA1",
|
||||
|
@ -48,11 +51,12 @@
|
|||
"uuid": "1748b48c-7496-4722-9048-17248e843773",
|
||||
"name": "Lil' Droplet",
|
||||
"issuer": "DigitalOcean",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "6CAIGVYB5MQ6TSZLJ56HJBWU5S3H7FUC",
|
||||
"algo": "SHA512",
|
||||
"digits": 6,
|
||||
"counter": 96
|
||||
"counter": 102
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -60,6 +64,7 @@
|
|||
"uuid": "9eef3fa3-bdde-4954-bf37-58bf1d5d6e5f",
|
||||
"name": "alex",
|
||||
"issuer": "TTRSS",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "BEBXDJJVXKX3ZMAZLJUU5I5PONI4IYAL",
|
||||
"algo": "SHA1",
|
||||
|
@ -72,6 +77,7 @@
|
|||
"uuid": "159b3e5f-fff4-4ff5-997a-b8d1263ee446",
|
||||
"name": "MoneroMan",
|
||||
"issuer": "Poloniex",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "NUSLOF6CFEWNPAARBVJ5WFA5YRRQU4FG",
|
||||
"algo": "SHA256",
|
||||
|
@ -84,6 +90,7 @@
|
|||
"uuid": "8cd843e5-4122-48e1-8089-a3d0c769741f",
|
||||
"name": "givemeabucket",
|
||||
"issuer": "Bitbucket",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "5KY3D2N53MRMIOMNKLLTNF3TDQJRDMEY",
|
||||
"algo": "SHA1",
|
||||
|
@ -96,6 +103,7 @@
|
|||
"uuid": "77c6267a-b38a-40b9-a3a7-5a5ce11230bb",
|
||||
"name": "Pepe",
|
||||
"issuer": "4chan",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "EIQMT7NHFYJUMBKQ35P34JGLG3MO7L2W",
|
||||
"algo": "SHA1",
|
||||
|
@ -108,6 +116,7 @@
|
|||
"uuid": "435a3542-c566-4cc0-a5f4-39cd6ffe316d",
|
||||
"name": "Delete Me",
|
||||
"issuer": "Facebook",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "GSQ7E5FO3UFFVANS3NSMFE7RTMRMYNCV",
|
||||
"algo": "SHA1",
|
||||
|
@ -120,6 +129,7 @@
|
|||
"uuid": "7dca76af-fa6a-4585-8d47-5a8b78130c9c",
|
||||
"name": "Oh wait, they don't have TOTP",
|
||||
"issuer": "Namecheap",
|
||||
"icon": null,
|
||||
"info": {
|
||||
"secret": "HIMV4HX3REXMUG236OOLN4GS7URI247Y",
|
||||
"algo": "SHA512",
|
||||
|
|
Loading…
Add table
Reference in a new issue