Merge pull request #72 from michaelschattgen/feature-selectentries

Add ability to select entries when importing
This commit is contained in:
Alexander Bakker 2019-05-01 13:14:16 +02:00 committed by GitHub
commit 33b4b4b7ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 569 additions and 114 deletions

View file

@ -3,58 +3,57 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.beemdevelopment.aegis">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name="com.beemdevelopment.aegis.AegisApplication"
android:name=".AegisApplication"
android:allowBackup="true"
android:icon="@mipmap/${iconName}"
android:label="Aegis"
android:supportsRtl="true"
tools:replace="android:theme"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="GoogleAppIndexingWarning">
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:theme">
<activity
android:name="com.beemdevelopment.aegis.ui.MainActivity"
android:name=".ui.SelectEntriesActivity"
android:label="Select entries"
android:theme="@style/AppTheme.TransparentActionBar"></activity>
<activity
android:name=".ui.MainActivity"
android:label="${title}"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.beemdevelopment.aegis.ui.ScannerActivity"
android:name=".ui.ScannerActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="Scan a QR code"
android:theme="@style/AppTheme.Fullscreen"
android:screenOrientation="portrait"
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
android:theme="@style/AppTheme.Fullscreen" />
<activity
android:name="com.beemdevelopment.aegis.ui.EditEntryActivity"
android:name=".ui.EditEntryActivity"
android:label="Edit profile"
android:theme="@style/AppTheme.NoActionBar">
</activity>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name="com.beemdevelopment.aegis.ui.IntroActivity"
android:theme="@style/Theme.Intro">
</activity>
<activity android:name="com.beemdevelopment.aegis.ui.AuthActivity">
</activity>
<activity android:name="com.beemdevelopment.aegis.ui.PreferencesActivity">
</activity>
android:name=".ui.IntroActivity"
android:theme="@style/Theme.Intro" />
<activity android:name=".ui.AuthActivity" />
<activity android:name=".ui.PreferencesActivity" />
<activity
android:name="com.beemdevelopment.aegis.ui.SlotManagerActivity"
android:name=".ui.SlotManagerActivity"
android:label="Manage key slots"
android:theme="@style/AppTheme.TransparentActionBar">
</activity>
android:theme="@style/AppTheme.TransparentActionBar" />
<activity
android:name="com.beemdevelopment.aegis.ui.GroupManagerActivity"
android:name=".ui.GroupManagerActivity"
android:label="Manage groups"
android:theme="@style/AppTheme.TransparentActionBar">
</activity>
android:theme="@style/AppTheme.TransparentActionBar" />
</application>
</manifest>

View file

@ -0,0 +1,49 @@
package com.beemdevelopment.aegis.helpers;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
public class FabScrollHelper {
private View _fabMenu;
private boolean _isAnimating;
public FabScrollHelper(View floatingActionsMenu) {
_fabMenu = floatingActionsMenu;
}
public void onScroll(int dx, int dy) {
if (dy > 0 && _fabMenu.getVisibility() == View.VISIBLE && !_isAnimating) {
_isAnimating = true;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) _fabMenu.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
_fabMenu.animate()
.translationY(_fabMenu.getHeight() + fabBottomMargin)
.setInterpolator(new AccelerateInterpolator(2))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
_isAnimating = false;
_fabMenu.setVisibility(View.INVISIBLE);
super.onAnimationEnd(animation);
}
}).start();
} else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !_isAnimating) {
_fabMenu.setVisibility(View.VISIBLE);
_fabMenu.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(2))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
_isAnimating = false;
super.onAnimationEnd(animation);
}
}).start();
}
}
}

View file

@ -30,6 +30,7 @@ import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
@ -79,7 +80,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private FloatingActionsMenu _fabMenu;
private EntryListView _entryListView;
private boolean _isAnimating;
private FabScrollHelper _fabScrollHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -118,6 +119,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_fabMenu.collapse();
startScanActivity();
});
_fabScrollHelper = new FabScrollHelper(_fabMenu);
}
@Override
@ -622,33 +625,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
@Override
public void onScroll(int dx, int dy) {
if (dy > 0 && _fabMenu.getVisibility() == View.VISIBLE && !_isAnimating) {
_isAnimating = true;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) _fabMenu.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
_fabMenu.animate()
.translationY(_fabMenu.getHeight() + fabBottomMargin)
.setInterpolator(new AccelerateInterpolator(2))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
_isAnimating = false;
_fabMenu.setVisibility(View.INVISIBLE);
super.onAnimationEnd(animation);
}
}).start();
} else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !_isAnimating) {
_fabMenu.setVisibility(View.VISIBLE);
_fabMenu.animate()
.translationY(0)
.setInterpolator(new DecelerateInterpolator(2))
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
_isAnimating = false;
super.onAnimationEnd(animation);
}
}).start();
}
_fabScrollHelper.onScroll(dx, dy);
}
}

View file

@ -2,15 +2,11 @@ package com.beemdevelopment.aegis.ui;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
@ -42,7 +38,6 @@ import com.beemdevelopment.aegis.importers.DatabaseImporterException;
import com.beemdevelopment.aegis.importers.DatabaseImporterResult;
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
import com.beemdevelopment.aegis.util.ByteInputStream;
import com.google.android.material.snackbar.Snackbar;
import com.takisoft.preferencex.PreferenceFragmentCompat;
import com.topjohnwu.superuser.Shell;
@ -52,7 +47,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@ -67,6 +61,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
private static final int CODE_IMPORT_DECRYPT = 1;
private static final int CODE_SLOTS = 2;
private static final int CODE_GROUPS = 3;
private static final int CODE_SELECT_ENTRIES = 4;
// permission request codes
private static final int CODE_PERM_IMPORT = 0;
@ -382,6 +377,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
break;
case CODE_GROUPS:
onGroupManagerResult(resultCode, data);
case CODE_SELECT_ENTRIES:
onSelectEntriesResult(resultCode, data);
break;
}
}
@ -524,44 +521,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
List<DatabaseEntry> entries = result.getEntries();
List<DatabaseImporterEntryException> errors = result.getErrors();
for (DatabaseEntry entry : entries) {
// temporary: randomize the UUID of duplicate entries and add them anyway
if (_db.getEntryByUUID(entry.getUUID()) != null) {
entry.resetUUID();
}
_db.addEntry(entry);
}
if (!saveDatabase()) {
return;
}
_result.putExtra("needsRecreate", true);
Snackbar bar = Snackbar.make(getView(), String.format(Locale.getDefault(), getString(R.string.imported_entries_count), entries.size(), errors.size()), Snackbar.LENGTH_LONG);
if (errors.size() > 0) {
bar.setAction(R.string.details, v -> {
List<String> messages = new ArrayList<>();
for (DatabaseImporterEntryException e : errors) {
messages.add(e.getMessage());
}
String message = TextUtils.join("\n\n", messages);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(R.string.import_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(android.R.string.copy, (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(getActivity(), getString(R.string.errors_copied), Toast.LENGTH_SHORT).show();
})
.create());
});
}
bar.show();
Intent intent = new Intent(getActivity(), SelectEntriesActivity.class);
intent.putExtra("entries", (ArrayList<DatabaseEntry>) entries);
intent.putExtra("errors", (ArrayList<DatabaseImporterEntryException>) errors);
startActivityForResult(intent, CODE_SELECT_ENTRIES);
}
private void onExport() {
@ -628,6 +591,31 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
}
private void onSelectEntriesResult(int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
List<DatabaseEntry> entries = (ArrayList<DatabaseEntry>) data.getSerializableExtra("entries");
for (DatabaseEntry entry : entries) {
// temporary: randomize the UUID of duplicate entries and add them anyway
if (_db.getEntryByUUID(entry.getUUID()) != null) {
entry.resetUUID();
}
_db.addEntry(entry);
}
if (!saveDatabase()) {
return;
}
String toastMessage = getResources().getString(R.string.imported_entries_count, entries.size());
Toast.makeText(getContext(), toastMessage, Toast.LENGTH_SHORT).show();
_result.putExtra("needsRecreate", true);
}
private boolean saveDatabase() {
try {
_db.save();

View file

@ -0,0 +1,138 @@
package com.beemdevelopment.aegis.ui;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
import com.beemdevelopment.aegis.ui.models.ImportEntry;
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
import com.getbase.floatingactionbutton.FloatingActionButton;
import java.util.ArrayList;
import java.util.List;
public class SelectEntriesActivity extends AegisActivity {
private ImportEntriesAdapter _adapter;
private FabScrollHelper _fabScrollHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_entries);
ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
_adapter = new ImportEntriesAdapter();
RecyclerView entriesView = findViewById(R.id.list_entries);
entriesView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
onScroll(dx, dy);
}
});
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
entriesView.setLayoutManager(layoutManager);
entriesView.setAdapter(_adapter);
entriesView.setNestedScrollingEnabled(false);
Intent intent = getIntent();
List<DatabaseEntry> entries = (ArrayList<DatabaseEntry>) intent.getSerializableExtra("entries");
List<DatabaseImporterEntryException> errors = (ArrayList<DatabaseImporterEntryException>) intent.getSerializableExtra("errors");
for (DatabaseEntry entry : entries) {
ImportEntry importEntry = new ImportEntry(entry);
_adapter.addEntry(importEntry);
}
if (errors.size() > 0) {
showErrorDialog(errors);
}
FloatingActionButton fabMenu = findViewById(R.id.fab);
fabMenu.setOnClickListener(v -> returnSelectedEntries());
_fabScrollHelper = new FabScrollHelper(fabMenu);
}
private void showErrorDialog(List<DatabaseImporterEntryException> errors) {
Dialogs.showSecureDialog(new AlertDialog.Builder(this)
.setTitle(R.string.import_error_title)
.setMessage(getString(R.string.import_error_dialog, errors.size()))
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(getString(R.string.details), (dialog, which) -> showDetailedErrorDialog(errors))
.create());
}
private void showDetailedErrorDialog(List<DatabaseImporterEntryException> errors) {
List<String> messages = new ArrayList<>();
for (DatabaseImporterEntryException e : errors) {
messages.add(e.getMessage());
}
String message = TextUtils.join("\n\n", messages);
Dialogs.showSecureDialog(new AlertDialog.Builder(this)
.setTitle(R.string.import_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(android.R.string.copy, (dialog2, which2) -> {
ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(this, getString(R.string.errors_copied), Toast.LENGTH_SHORT).show();
})
.create());
}
private void returnSelectedEntries() {
List<DatabaseEntry> entries = _adapter.getSelectedEntries();
Intent intent = new Intent();
intent.putExtra("entries", (ArrayList<DatabaseEntry>) entries);
setResult(RESULT_OK, intent);
finish();
}
public void onScroll(int dx, int dy) {
_fabScrollHelper.onScroll(dx, dy);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_select_entries, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
case R.id.toggle_checkboxes:
_adapter.toggleCheckboxes();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
}

View file

@ -0,0 +1,37 @@
package com.beemdevelopment.aegis.ui.models;
import com.beemdevelopment.aegis.db.DatabaseEntry;
public class ImportEntry {
private DatabaseEntry _entry;
private boolean _isChecked = true;
private Listener _listener;
public ImportEntry(DatabaseEntry entry) {
_entry = entry;
}
public void setOnCheckedChangedListener(Listener listener) {
_listener = listener;
}
public DatabaseEntry getDatabaseEntry() {
return _entry;
}
public boolean isChecked() {
return _isChecked;
}
public void setIsChecked(boolean isChecked) {
_isChecked = isChecked;
if (_listener != null) {
_listener.onCheckedChanged(_isChecked);
}
}
public interface Listener {
void onCheckedChanged(boolean value);
}
}

View file

@ -0,0 +1,97 @@
package com.beemdevelopment.aegis.ui.views;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.ui.models.ImportEntry;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class ImportEntriesAdapter extends RecyclerView.Adapter<ImportEntryHolder> {
private List<ImportEntry> _entries;
public ImportEntriesAdapter() {
_entries = new ArrayList<>();
}
public void addEntry(ImportEntry entry) {
_entries.add(entry);
int position = getItemCount() - 1;
if (position == 0) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
@NonNull
@Override
public ImportEntryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_import_entry, parent, false);
return new ImportEntryHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ImportEntryHolder holder, int position) {
ImportEntry entry = _entries.get(position);
entry.setOnCheckedChangedListener(holder);
holder.setData(entry);
}
@Override
public void onViewRecycled(@NonNull ImportEntryHolder holder) {
holder.getEntry().setOnCheckedChangedListener(null);
}
@Override
public int getItemCount() {
return _entries.size();
}
public List<DatabaseEntry> getSelectedEntries() {
List<DatabaseEntry> entries = new ArrayList<>();
for (ImportEntry entry : getCheckedEntries()) {
entries.add(entry.getDatabaseEntry());
}
return entries;
}
private List<ImportEntry> getCheckedEntries() {
List<ImportEntry> entries = new ArrayList<>();
for (ImportEntry entry : _entries) {
if (entry.isChecked()) {
entries.add(entry);
}
}
return entries;
}
public void toggleCheckboxes() {
int checkedEntries = getCheckedEntries().size();
if (checkedEntries == 0 || checkedEntries != _entries.size()) {
setCheckboxStates(true);
} else {
setCheckboxStates(false);
}
}
private void setCheckboxStates(boolean checked) {
for (ImportEntry entry: _entries) {
if (entry.isChecked() != checked) {
entry.setIsChecked(checked);
}
}
}
}

View file

@ -0,0 +1,48 @@
package com.beemdevelopment.aegis.ui.views;
import android.content.Context;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.ui.models.ImportEntry;
import androidx.recyclerview.widget.RecyclerView;
public class ImportEntryHolder extends RecyclerView.ViewHolder implements ImportEntry.Listener {
private TextView _issuer;
private TextView _accountName;
private CheckBox _checkbox;
private ImportEntry _entry;
public ImportEntryHolder(final View view) {
super(view);
_issuer = view.findViewById(R.id.profile_issuer);
_accountName = view.findViewById(R.id.profile_account_name);
_checkbox = view.findViewById(R.id.checkbox_import_entry);
view.setOnClickListener(v -> _entry.setIsChecked(!_entry.isChecked()));
}
public void setData(ImportEntry entry) {
_entry = entry;
Context context = itemView.getContext();
DatabaseEntry dbEntry = entry.getDatabaseEntry();
_issuer.setText(!dbEntry.getIssuer().isEmpty() ? dbEntry.getIssuer() : context.getString(R.string.unknown_issuer));
_accountName.setText(!dbEntry.getName().isEmpty() ? dbEntry.getName() : context.getString(R.string.unknown_account_name));
_checkbox.setChecked(entry.isChecked());
}
public ImportEntry getEntry() {
return _entry;
}
@Override
public void onCheckedChanged(boolean value) {
_checkbox.setChecked(value);
}
}

View file

@ -1,9 +1,5 @@
<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 android:height="24dp" android:tint="#F7F7F7"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#F7F7F7"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
</vector>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/background"
android:id="@+id/importEntriesRootView"
tools:context="com.beemdevelopment.aegis.ui.SelectEntriesActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_entries"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackground"
android:paddingBottom="60dp"
android:clipToPadding="false"
android:scrollbars="vertical"/>
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:fab_icon="@drawable/ic_check_black_24dp"
app:fab_colorNormal="@color/colorAccent"
app:fab_colorPressed="@color/colorAccent"
app:fab_labelStyle="@style/fab_label_style"
app:fab_labelsPosition="left">
</com.getbase.floatingactionbutton.FloatingActionButton>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:background="?attr/cardBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentBottom="false"
android:layout_alignParentTop="false"
android:layout_alignWithParentIfMissing="false"
android:gravity="bottom"
android:id="@+id/relativeLayout"
android:layout_alignParentStart="false"
android:paddingTop="12dp"
android:paddingBottom="16dp"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp">
<TextView
android:id="@+id/profile_issuer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp"
android:fontFamily="sans-serif-light"
android:includeFontPadding="false"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/primaryText"
android:textSize="18sp"
android:textStyle="normal|bold"
tools:text="@string/issuer" />
<TextView
android:id="@+id/profile_account_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profile_issuer"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/extra_info_text"
android:textSize="14sp"
tools:text="AccountName" />
<CheckBox
android:id="@+id/checkbox_import_entry"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp" />
</RelativeLayout>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item android:id="@+id/toggle_checkboxes"
android:icon="@drawable/ic_done_all_black_24dp"
android:title="@string/toggle_checkboxes"
app:showAsAction="always" />
</menu>

View file

@ -109,7 +109,7 @@
<string name="file_not_found">Fout: Bestand niet gevonden</string>
<string name="reading_file_error">Er is een fout opgetreden tijdens het lezen van het bestand</string>
<string name="root_error">Fout: Er kon geen root-access verkregen worden</string>
<string name="imported_entries_count">%d items geïmporteerd. %d fouten.</string>
<string name="imported_entries_count">%d items geïmporteerd</string>
<string name="import_error_title">Er zijn fouten opgetreden tijdens het importeren</string>
<string name="exporting_database_error">Er is een fout opgetreden tijdens het exporteren van de database</string>
<string name="export_database_location">De database is geëxporteerd naar:</string>

View file

@ -108,7 +108,7 @@
<string name="file_not_found">Ошибка: файл не найден</string>
<string name="reading_file_error">Произошла ошибка при попытке прочитать файл</string>
<string name="root_error">Ошибка: невозможно получить root-доступ</string>
<string name="imported_entries_count">Импортироаано %d записей. %d ошибки.</string>
<string name="imported_entries_count">Импортироаано %d записей</string>
<string name="exporting_database_error">Произошла ошибка при попытке экспорта базы данных</string>
<string name="export_database_location">База данных была экспортирована в:</string>
<string name="export_warning">Это действие экспортирует базу данных из личного хранилища Aegis.</string>

View file

@ -114,7 +114,8 @@
<string name="file_not_found">Error: File not found</string>
<string name="reading_file_error">An error occurred while trying to read the file</string>
<string name="root_error">Error: unable to obtain root access</string>
<string name="imported_entries_count">Imported %d entries. %d errors.</string>
<string name="imported_entries_count">Imported %d entries</string>
<string name="read_entries_count">Read %d entries. %d errors.</string>
<string name="import_error_title">One or more errors occurred during the import</string>
<string name="exporting_database_error">An error occurred while trying to export the database</string>
<string name="export_database_location">The database has been exported to:</string>
@ -156,6 +157,10 @@
<string name="normal_viewmode_title">Normal</string>
<string name="compact_mode_title">Compact</string>
<string name="small_mode_title">Small</string>
<string name="unknown_issuer">Unknown issuer</string>
<string name="unknown_account_name">Unknown account name</string>
<string name="import_error_dialog">Aegis could not import %d tokens. These tokens will be skipped. Press \'details\' to see more information about the errors.</string>
<string name="unable_to_read_qrcode">Unable to read and process QR code</string>
<string name="select_picture">Select picture</string>
<string name="toggle_checkboxes">Toggle checkboxes</string>
</resources>