mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-04 20:30:36 +00:00
Add ability to select entries when importing
Move onScroll to seperate class to avoid duplicate code Move importing logic back to the PreferencesFragment Add minor changes Add ImportEntry to properly track checked states Minor layout changes
This commit is contained in:
parent
24a93ecc9f
commit
21fd8fdd8d
16 changed files with 569 additions and 114 deletions
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
5
app/src/main/res/drawable/ic_done_all_black_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_done_all_black_24dp.xml
Normal 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>
|
33
app/src/main/res/layout/activity_select_entries.xml
Normal file
33
app/src/main/res/layout/activity_select_entries.xml
Normal 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>
|
76
app/src/main/res/layout/card_import_entry.xml
Normal file
76
app/src/main/res/layout/card_import_entry.xml
Normal 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>
|
8
app/src/main/res/menu/menu_select_entries.xml
Normal file
8
app/src/main/res/menu/menu_select_entries.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue