From ad77bd687fc8ebf1cce0f0b9fabcf44a3d784211 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 10 Oct 2022 22:32:30 +0200 Subject: [PATCH] Add support for predictive back gesture --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 3 +- .../aegis/helpers/SafHelper.java | 20 +++ .../aegis/helpers/SimpleTextWatcher.java | 33 ++++ .../aegis/ui/AboutActivity.java | 2 +- .../aegis/ui/AuthActivity.java | 25 ++- .../aegis/ui/EditEntryActivity.java | 133 ++++++++------ .../aegis/ui/GroupManagerActivity.java | 136 +++++++++----- .../aegis/ui/ImportEntriesActivity.java | 2 +- .../aegis/ui/MainActivity.java | 170 +++++++++++------- .../aegis/ui/PreferencesActivity.java | 29 +-- .../aegis/ui/TransferEntriesActivity.java | 2 +- .../AppearancePreferencesFragment.java | 31 +--- .../aegis/ui/intro/IntroBaseActivity.java | 18 +- .../menu/{menu_slots.xml => menu_groups.xml} | 2 +- 15 files changed, 395 insertions(+), 212 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/helpers/SimpleTextWatcher.java rename app/src/main/res/menu/{menu_slots.xml => menu_groups.xml} (82%) diff --git a/app/build.gradle b/app/build.gradle index 74932fc5..ac0507d3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -131,6 +131,7 @@ dependencies { annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.activity:activity:1.6.0' implementation 'androidx.appcompat:appcompat:1.5.1' implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.camera:camera-camera2:$cameraxVersion" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fa7e8349..ceff09b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,12 +20,13 @@ android:fullBackupContent="@xml/backup_rules_old" android:dataExtractionRules="@xml/backup_rules" android:backupAgent=".AegisBackupAgent" + android:enableOnBackInvokedCallback="true" android:icon="@mipmap/${iconName}" android:label="Aegis" android:supportsRtl="true" android:theme="@style/Theme.Aegis.Launch" tools:replace="android:theme" - tools:targetApi="s"> + tools:targetApi="tiramisu"> { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { decryptButton.performClick(); @@ -102,7 +105,9 @@ public class AuthActivity extends AegisActivity { } if (_vaultManager.getVaultFileError() != null) { - Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog, which) -> onBackPressed()); + Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog, which) -> { + getOnBackPressedDispatcher().onBackPressed(); + }); return; } @@ -179,11 +184,6 @@ public class AuthActivity extends AegisActivity { imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } - @Override - public void onBackPressed() { - finishAffinity(); - } - @Override public void onResume() { super.onResume(); @@ -301,6 +301,19 @@ public class AuthActivity extends AegisActivity { } } + private class BackPressHandler extends OnBackPressedCallback { + public BackPressHandler() { + super(true); + } + + @Override + public void handleOnBackPressed() { + // This breaks predictive back gestures, but it doesn't make sense + // to go back to MainActivity when cancelling auth + finishAffinity(); + } + } + private class PasswordDerivationListener implements PasswordSlotDecryptTask.Callback { @Override public void onTaskFinished(PasswordSlotDecryptTask.Result result) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index dac4e220..d6584826 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -7,7 +7,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; @@ -16,18 +15,17 @@ import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; -import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.AutoCompleteTextView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; -import androidx.documentfile.provider.DocumentFile; import com.amulyakhare.textdrawable.TextDrawable; import com.avito.android.krop.KropView; @@ -38,6 +36,8 @@ import com.beemdevelopment.aegis.encoding.Hex; import com.beemdevelopment.aegis.helpers.DropdownHelper; import com.beemdevelopment.aegis.helpers.EditTextHelper; import com.beemdevelopment.aegis.helpers.IconViewHelper; +import com.beemdevelopment.aegis.helpers.SafHelper; +import com.beemdevelopment.aegis.helpers.SimpleTextWatcher; import com.beemdevelopment.aegis.helpers.TextDrawableHelper; import com.beemdevelopment.aegis.icons.IconPack; import com.beemdevelopment.aegis.icons.IconType; @@ -93,7 +93,6 @@ public class EditEntryActivity extends AegisActivity { // keep track of icon changes separately as the generated jpeg's are not deterministic private boolean _hasChangedIcon = false; private IconPack.Icon _selectedIcon; - private boolean _isEditingIcon; private CircleImageView _iconView; private ImageView _saveImageButton; @@ -120,6 +119,9 @@ public class EditEntryActivity extends AegisActivity { private RelativeLayout _advancedSettingsHeader; private RelativeLayout _advancedSettings; + private BackPressHandler _backPressHandler; + private IconBackPressHandler _iconBackPressHandler; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -132,8 +134,15 @@ public class EditEntryActivity extends AegisActivity { _groups = _vaultManager.getVault().getGroups(); ActionBar bar = getSupportActionBar(); - bar.setHomeAsUpIndicator(R.drawable.ic_close); - bar.setDisplayHomeAsUpEnabled(true); + if (bar != null) { + bar.setHomeAsUpIndicator(R.drawable.ic_close); + bar.setDisplayHomeAsUpEnabled(true); + } + + _backPressHandler = new BackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _backPressHandler); + _iconBackPressHandler = new IconBackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _iconBackPressHandler); // retrieve info from the calling activity Intent intent = getIntent(); @@ -254,9 +263,21 @@ public class EditEntryActivity extends AegisActivity { String group = _origEntry.getGroup(); setGroup(group); - // update the icon if the text changed - _textIssuer.addTextChangedListener(_iconChangeListener); - _textName.addTextChangedListener(_iconChangeListener); + // Update the icon if the issuer or name has changed + _textIssuer.addTextChangedListener(_nameChangeListener); + _textName.addTextChangedListener(_nameChangeListener); + + // Register listeners to trigger validation + _textIssuer.addTextChangedListener(_validationListener); + _textName.addTextChangedListener(_validationListener); + _textNote.addTextChangedListener(_validationListener); + _textSecret.addTextChangedListener(_validationListener); + _dropdownType.addTextChangedListener(_validationListener); + _dropdownGroup.addTextChangedListener(_validationListener); + _dropdownAlgo.addTextChangedListener(_validationListener); + _textPeriodCounter.addTextChangedListener(_validationListener); + _textDigits.addTextChangedListener(_validationListener); + _textPin.addTextChangedListener(_validationListener); // show/hide period and counter fields on type change _dropdownType.setOnItemClickListener((parent, view, position, id) -> { @@ -403,30 +424,26 @@ public class EditEntryActivity extends AegisActivity { _dropdownGroupList.add(res.getString(R.string.new_group)); } - @Override - public void onBackPressed() { - if (_isEditingIcon) { - stopEditingIcon(false); - return; - } + private boolean hasUnsavedChanges(VaultEntry newEntry) { + return _hasChangedIcon || !_origEntry.equals(newEntry); + } + private void discardAndFinish() { AtomicReference msg = new AtomicReference<>(); AtomicReference entry = new AtomicReference<>(); - try { entry.set(parseEntry()); } catch (ParseException e) { msg.set(e.getMessage()); } - // close the activity if the entry has not been changed - if (!_hasChangedIcon && _origEntry.equals(entry.get())) { - super.onBackPressed(); + if (!hasUnsavedChanges(entry.get())) { + finish(); return; } // ask for confirmation if the entry has been changed - Dialogs.showDiscardDialog(this, + Dialogs.showDiscardDialog(EditEntryActivity.this, (dialog, which) -> { // if the entry couldn't be parsed, we show an error dialog if (msg.get() != null) { @@ -436,7 +453,7 @@ public class EditEntryActivity extends AegisActivity { addAndFinish(entry.get()); }, - (dialog, which) -> super.onBackPressed() + (dialog, which) -> finish() ); } @@ -444,7 +461,7 @@ public class EditEntryActivity extends AegisActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - onBackPressed(); + discardAndFinish(); break; case R.id.action_save: onSave(); @@ -558,7 +575,7 @@ public class EditEntryActivity extends AegisActivity { stopEditingIcon(true); }); - _isEditingIcon = true; + _iconBackPressHandler.setEnabled(true); } private void stopEditingIcon(boolean save) { @@ -570,7 +587,7 @@ public class EditEntryActivity extends AegisActivity { _hasCustomIcon = _hasCustomIcon || save; _hasChangedIcon = save; - _isEditingIcon = false; + _iconBackPressHandler.setEnabled(false); } @Override @@ -620,7 +637,7 @@ public class EditEntryActivity extends AegisActivity { @Override protected void onActivityResult(int requestCode, final int resultCode, Intent data) { if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) { - String fileType = getMimeType(data.getData()); + String fileType = SafHelper.getMimeType(this, data.getData()); if (fileType != null && fileType.equals(IconType.SVG.toMimeType())) { ImportFileTask.Params params = new ImportFileTask.Params(data.getData(), "icon", null); ImportFileTask task = new ImportFileTask(this, result -> { @@ -640,23 +657,6 @@ public class EditEntryActivity extends AegisActivity { super.onActivityResult(requestCode, resultCode, data); } - private String getMimeType(Uri uri) { - DocumentFile file = DocumentFile.fromSingleUri(this, uri); - if (file != null) { - String fileType = file.getType(); - if (fileType != null) { - return fileType; - } - - String ext = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); - if (ext != null) { - return MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext); - } - } - - return null; - } - private int parsePeriod() throws ParseException { try { return Integer.parseInt(_textPeriodCounter.getText().toString()); @@ -792,7 +792,7 @@ public class EditEntryActivity extends AegisActivity { } private boolean onSave() { - if (_isEditingIcon) { + if (_iconBackPressHandler.isEnabled()) { stopEditingIcon(true); } @@ -819,23 +819,50 @@ public class EditEntryActivity extends AegisActivity { } } - private final TextWatcher _iconChangeListener = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + private final TextWatcher _validationListener = new SimpleTextWatcher((s) -> { + updateBackPressHandlerState(); + }); + + private final TextWatcher _nameChangeListener = new SimpleTextWatcher((s) -> { + if (!_hasCustomIcon) { + TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView); + _iconView.setImageDrawable(drawable); + } + }); + + private void updateBackPressHandlerState() { + VaultEntry entry = null; + try { + entry = parseEntry(); + } catch (ParseException ignored) { + + } + + boolean backEnabled = hasUnsavedChanges(entry); + _backPressHandler.setEnabled(backEnabled); + } + + private class BackPressHandler extends OnBackPressedCallback { + public BackPressHandler() { + super(false); } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void handleOnBackPressed() { + discardAndFinish(); + } + } + + private class IconBackPressHandler extends OnBackPressedCallback { + public IconBackPressHandler() { + super(false); } @Override - public void afterTextChanged(Editable s) { - if (!_hasCustomIcon) { - TextDrawable drawable = TextDrawableHelper.generate(_textIssuer.getText().toString(), _textName.getText().toString(), _iconView); - _iconView.setImageDrawable(drawable); - } + public void handleOnBackPressed() { + stopEditingIcon(false); } - }; + } private static class ParseException extends Exception { public ParseException(String message) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java index e842c6fb..74495cac 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java @@ -1,10 +1,12 @@ package com.beemdevelopment.aegis.ui; -import android.content.Intent; import android.os.Bundle; +import android.view.Menu; import android.view.MenuItem; import android.view.View; +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -12,16 +14,19 @@ import androidx.recyclerview.widget.RecyclerView; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.views.GroupAdapter; +import com.beemdevelopment.aegis.vault.VaultEntry; -import java.text.Collator; import java.util.ArrayList; -import java.util.TreeSet; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; public class GroupManagerActivity extends AegisActivity implements GroupAdapter.Listener { private GroupAdapter _adapter; - private TreeSet _groups; + private HashSet _removedGroups; private RecyclerView _slotsView; private View _emptyStateView; + private BackPressHandler _backPressHandler; @Override protected void onCreate(Bundle savedInstanceState) { @@ -31,17 +36,20 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter. } setContentView(R.layout.activity_groups); setSupportActionBar(findViewById(R.id.toolbar)); - - Intent intent = getIntent(); - _groups = new TreeSet<>(Collator.getInstance()); - _groups.addAll(intent.getStringArrayListExtra("groups")); - if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } + _backPressHandler = new BackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _backPressHandler); + + if (savedInstanceState != null) { + List groups = savedInstanceState.getStringArrayList("removedGroups"); + _removedGroups = new HashSet<>(Objects.requireNonNull(groups)); + } else { + _removedGroups = new HashSet<>(); + } - // set up the recycler view _adapter = new GroupAdapter(this); _slotsView= findViewById(R.id.list_slots); LinearLayoutManager layoutManager = new LinearLayoutManager(this); @@ -49,7 +57,7 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter. _slotsView.setAdapter(_adapter); _slotsView.setNestedScrollingEnabled(false); - for (String group : _groups) { + for (String group : _vaultManager.getVault().getGroups()) { _adapter.addGroup(group); } @@ -57,6 +65,74 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter. updateEmptyState(); } + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putStringArrayList("removedGroups", new ArrayList<>(_removedGroups)); + } + + @Override + public void onRemoveGroup(String group) { + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.remove_group) + .setMessage(R.string.remove_group_description) + .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { + _removedGroups.add(group); + _adapter.removeGroup(group); + _backPressHandler.setEnabled(true); + updateEmptyState(); + }) + .setNegativeButton(android.R.string.no, null) + .create()); + } + + private void saveAndFinish() { + if (!_removedGroups.isEmpty()) { + for (VaultEntry entry : _vaultManager.getVault().getEntries()) { + if (_removedGroups.contains(entry.getGroup())) { + entry.setGroup(null); + } + } + + saveAndBackupVault(); + } + + finish(); + } + + private void discardAndFinish() { + if (_removedGroups.isEmpty()) { + finish(); + return; + } + + Dialogs.showDiscardDialog(this, + (dialog, which) -> saveAndFinish(), + (dialog, which) -> finish()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_groups, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + discardAndFinish(); + break; + case R.id.action_save: + saveAndFinish(); + break; + default: + return super.onOptionsItemSelected(item); + } + + return true; + } + private void updateEmptyState() { if (_adapter.getItemCount() > 0) { _slotsView.setVisibility(View.VISIBLE); @@ -67,38 +143,14 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter. } } - @Override - public void onRemoveGroup(String group) { - Dialogs.showSecureDialog(new AlertDialog.Builder(this) - .setTitle(R.string.remove_group) - .setMessage(R.string.remove_group_description) - .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { - _groups.remove(group); - _adapter.removeGroup(group); - updateEmptyState(); - }) - .setNegativeButton(android.R.string.no, null) - .create()); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - break; - default: - return super.onOptionsItemSelected(item); + private class BackPressHandler extends OnBackPressedCallback { + public BackPressHandler() { + super(false); } - return true; - } - - @Override - public void onBackPressed() { - Intent intent = new Intent(); - intent.putExtra("groups", new ArrayList<>(_groups)); - setResult(RESULT_OK, intent); - finish(); + @Override + public void handleOnBackPressed() { + discardAndFinish(); + } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index 324652d4..87e7bb87 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -236,7 +236,7 @@ public class ImportEntriesActivity extends AegisActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - onBackPressed(); + finish(); break; case R.id.toggle_checkboxes: _adapter.toggleCheckboxes(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index 2f343f5e..9b57f568 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -29,6 +29,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; @@ -47,11 +48,11 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment; import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment; import com.beemdevelopment.aegis.ui.tasks.QrDecodeTask; -import com.beemdevelopment.aegis.ui.views.EntryHolder; import com.beemdevelopment.aegis.ui.views.EntryListView; import com.beemdevelopment.aegis.vault.VaultEntry; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.common.base.Strings; import java.util.ArrayList; import java.util.Collections; @@ -81,12 +82,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private boolean _isDoingIntro; private boolean _isAuthenticating; - private String _submittedSearchSubtitle; - private String _searchQueryInputText; - private String _activeSearchFilter; + private String _submittedSearchQuery; + private String _pendingSearchQuery; private List _selectedEntries; - private ActionMode _actionMode; private Menu _menu; private SearchView _searchView; @@ -96,8 +95,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private FabScrollHelper _fabScrollHelper; + private ActionMode _actionMode; private ActionMode.Callback _actionModeCallbacks = new ActionModeCallbacks(); + private LockBackPressHandler _lockBackPressHandler; + private SearchViewBackPressHandler _searchViewBackPressHandler; + private ActionModeBackPressHandler _actionModeBackPressHandler; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -110,13 +114,19 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene if (savedInstanceState != null) { _isRecreated = true; - _searchQueryInputText = savedInstanceState.getString("searchQueryInputText"); - _activeSearchFilter = savedInstanceState.getString("activeSearchFilter"); - _submittedSearchSubtitle = savedInstanceState.getString("submittedSearchSubtitle"); + _pendingSearchQuery = savedInstanceState.getString("pendingSearchQuery"); + _submittedSearchQuery = savedInstanceState.getString("submittedSearchQuery"); _isDoingIntro = savedInstanceState.getBoolean("isDoingIntro"); _isAuthenticating = savedInstanceState.getBoolean("isAuthenticating"); } + _lockBackPressHandler = new LockBackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _lockBackPressHandler); + _searchViewBackPressHandler = new SearchViewBackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _searchViewBackPressHandler); + _actionModeBackPressHandler = new ActionModeBackPressHandler(); + getOnBackPressedDispatcher().addCallback(this, _actionModeBackPressHandler); + _entryListView = (EntryListView) getSupportFragmentManager().findFragmentById(R.id.key_profiles); _entryListView.setListener(this); _entryListView.setCodeGroupSize(_prefs.getCodeGroupSize()); @@ -178,9 +188,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene @Override protected void onSaveInstanceState(@NonNull Bundle instance) { super.onSaveInstanceState(instance); - instance.putString("activeSearchFilter", _activeSearchFilter); - instance.putString("submittedSearchSubtitle", _submittedSearchSubtitle); - instance.putString("searchQueryInputText", _searchQueryInputText); + instance.putString("pendingSearchQuery", _pendingSearchQuery); + instance.putString("submittedSearchQuery", _submittedSearchQuery); instance.putBoolean("isDoingIntro", _isDoingIntro); instance.putBoolean("isAuthenticating", _isAuthenticating); } @@ -619,33 +628,16 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene checkTimeSyncSetting(); } + _lockBackPressHandler.setEnabled( + _vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON) + ); + handleIncomingIntent(); updateLockIcon(); doShortcutActions(); updateErrorBar(); } - @Override - public void onBackPressed() { - if (!_searchView.isIconified() || _submittedSearchSubtitle != null) { - _submittedSearchSubtitle = null; - _entryListView.setSearchFilter(null); - _activeSearchFilter = null; - - collapseSearchView(); - setTitle(R.string.app_name); - getSupportActionBar().setSubtitle(null); - return; - } - - if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) { - _vaultManager.lock(false); - return; - } - - super.onBackPressed(); - } - private void deleteEntries(List entries) { for (VaultEntry entry: entries) { VaultEntry oldEntry = _vaultManager.getVault().removeEntry(entry); @@ -669,56 +661,63 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _searchView = (SearchView) searchViewMenuItem.getActionView(); _searchView.setMaxWidth(Integer.MAX_VALUE); + _searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> { + boolean enabled = _submittedSearchQuery != null || hasFocus; + _searchViewBackPressHandler.setEnabled(enabled); + }); + _searchView.setOnCloseListener(() -> { + boolean enabled = _submittedSearchQuery != null; + _searchViewBackPressHandler.setEnabled(enabled); + return false; + }); _searchView.setQueryHint(getString(R.string.search)); - if (_prefs.getFocusSearchEnabled() && !_isRecreated) { - _searchView.setIconified(false); - _searchView.setFocusable(true); - _searchView.requestFocus(); - _searchView.requestFocusFromTouch(); - } - _searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String s) { setTitle(getString(R.string.search)); getSupportActionBar().setSubtitle(s); - _searchQueryInputText = null; - _submittedSearchSubtitle = s; + _entryListView.setSearchFilter(s); + _pendingSearchQuery = null; + _submittedSearchQuery = s; collapseSearchView(); + _searchViewBackPressHandler.setEnabled(true); return false; } @Override public boolean onQueryTextChange(String s) { - if (_submittedSearchSubtitle == null) { + if (_submittedSearchQuery == null) { _entryListView.setSearchFilter(s); - _activeSearchFilter = s; - _searchQueryInputText = s; } + + _pendingSearchQuery = Strings.isNullOrEmpty(s) && !_searchView.isIconified() ? null : s; + if (_pendingSearchQuery != null) { + _entryListView.setSearchFilter(_pendingSearchQuery); + } + return false; } }); - _searchView.setOnSearchClickListener(v -> { - if (_submittedSearchSubtitle != null) { - _entryListView.setSearchFilter(null); - _activeSearchFilter = null; - _submittedSearchSubtitle = null; - } + String query = _submittedSearchQuery != null ? _submittedSearchQuery : _pendingSearchQuery; + _searchView.setQuery(query, false); }); - if (_submittedSearchSubtitle != null) { - getSupportActionBar().setSubtitle(_submittedSearchSubtitle); - } - - if (_activeSearchFilter != null) { - _entryListView.setSearchFilter(_activeSearchFilter); - } - - if (_searchQueryInputText != null) { - _searchView.setQuery(_searchQueryInputText, false); + if (_pendingSearchQuery != null) { _searchView.setIconified(false); + _searchView.setQuery(_pendingSearchQuery, false); + _searchViewBackPressHandler.setEnabled(true); + } else if (_submittedSearchQuery != null) { + setTitle(getString(R.string.search)); + getSupportActionBar().setSubtitle(_submittedSearchQuery); + _entryListView.setSearchFilter(_submittedSearchQuery); + _searchViewBackPressHandler.setEnabled(true); + } else if (_prefs.getFocusSearchEnabled() && !_isRecreated) { + _searchView.setIconified(false); + _searchView.setFocusable(true); + _searchView.requestFocus(); + _searchView.requestFocusFromTouch(); } return true; @@ -897,7 +896,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene _selectedEntries.add(entry); _entryListView.setActionModeState(true, entry); + startActionMode(); + } + + private void startActionMode() { _actionMode = startSupportActionMode(_actionModeCallbacks); + _actionModeBackPressHandler.setEnabled(true); } @Override @@ -975,6 +979,51 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene } } + private class SearchViewBackPressHandler extends OnBackPressedCallback { + public SearchViewBackPressHandler() { + super(false); + } + + @Override + public void handleOnBackPressed() { + if (!_searchView.isIconified() || _submittedSearchQuery != null) { + _submittedSearchQuery = null; + _pendingSearchQuery = null; + _entryListView.setSearchFilter(null); + + collapseSearchView(); + setTitle(R.string.app_name); + getSupportActionBar().setSubtitle(null); + } + } + } + + private class LockBackPressHandler extends OnBackPressedCallback { + public LockBackPressHandler() { + super(false); + } + + @Override + public void handleOnBackPressed() { + if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) { + _vaultManager.lock(false); + } + } + } + + private class ActionModeBackPressHandler extends OnBackPressedCallback { + public ActionModeBackPressHandler() { + super(false); + } + + @Override + public void handleOnBackPressed() { + if (_actionMode != null) { + _actionMode.finish(); + } + } + } + private class ActionModeCallbacks implements ActionMode.Callback { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { @@ -1044,6 +1093,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene @Override public void onDestroyActionMode(ActionMode mode) { _entryListView.setActionModeState(false, null); + _actionModeBackPressHandler.setEnabled(false); _selectedEntries.clear(); _actionMode = null; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java index 60068adb..b24e2610 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java @@ -3,7 +3,9 @@ package com.beemdevelopment.aegis.ui; import android.os.Bundle; import android.view.MenuItem; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; @@ -23,6 +25,8 @@ public class PreferencesActivity extends AegisActivity implements } setContentView(R.layout.activity_preferences); setSupportActionBar(findViewById(R.id.toolbar)); + getSupportFragmentManager() + .registerFragmentLifecycleCallbacks(new FragmentResumeListener(), true); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -33,7 +37,7 @@ public class PreferencesActivity extends AegisActivity implements _fragment = new MainPreferencesFragment(); _fragment.setArguments(getIntent().getExtras()); - getSupportFragmentManager().beginTransaction() + getSupportFragmentManager().beginTransaction() .replace(R.id.content, _fragment) .commit(); @@ -48,13 +52,7 @@ public class PreferencesActivity extends AegisActivity implements } @Override - public void onBackPressed() { - super.onBackPressed(); - setTitle(R.string.action_settings); - } - - @Override - protected void onRestoreInstanceState(final Bundle inState) { + protected void onRestoreInstanceState(@NonNull final Bundle inState) { if (_fragment instanceof PreferencesFragment) { // pass the stored result intent back to the fragment if (inState.containsKey("result")) { @@ -65,7 +63,7 @@ public class PreferencesActivity extends AegisActivity implements } @Override - protected void onSaveInstanceState(final Bundle outState) { + protected void onSaveInstanceState(@NonNull final Bundle outState) { if (_fragment instanceof PreferencesFragment) { // save the result intent of the fragment // this is done so we don't lose anything if the fragment calls recreate on this activity @@ -77,7 +75,7 @@ public class PreferencesActivity extends AegisActivity implements @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { - onBackPressed(); + finish(); } else { return super.onOptionsItemSelected(item); } @@ -86,7 +84,7 @@ public class PreferencesActivity extends AegisActivity implements } @Override - public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { + public boolean onPreferenceStartFragment(@NonNull PreferenceFragmentCompat caller, Preference pref) { _fragment = getSupportFragmentManager().getFragmentFactory().instantiate(getClassLoader(), pref.getFragment()); _fragment.setArguments(pref.getExtras()); _fragment.setTargetFragment(caller, 0); @@ -117,4 +115,13 @@ public class PreferencesActivity extends AegisActivity implements throw new RuntimeException(e); } } + + private class FragmentResumeListener extends FragmentManager.FragmentLifecycleCallbacks { + @Override + public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) { + if (f instanceof MainPreferencesFragment) { + setTitle(R.string.action_settings); + } + } + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java index 756ce1ee..1887c269 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java @@ -97,7 +97,7 @@ public class TransferEntriesActivity extends AegisActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - onBackPressed(); + finish(); break; default: return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java index 20140d88..1ffca8a0 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java @@ -1,6 +1,5 @@ package com.beemdevelopment.aegis.ui.fragments.preferences; -import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; @@ -13,10 +12,8 @@ import com.beemdevelopment.aegis.Theme; import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.ui.GroupManagerActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.vault.VaultEntry; import java.util.ArrayList; -import java.util.HashSet; public class AppearancePreferencesFragment extends PreferencesFragment { private Preference _groupsPreference; @@ -30,8 +27,7 @@ public class AppearancePreferencesFragment extends PreferencesFragment { _groupsPreference = requirePreference("pref_groups"); _groupsPreference.setOnPreferenceClickListener(preference -> { Intent intent = new Intent(requireActivity(), GroupManagerActivity.class); - intent.putExtra("groups", new ArrayList<>(_vaultManager.getVault().getGroups())); - startActivityForResult(intent, CODE_GROUPS); + startActivity(intent); return true; }); @@ -114,29 +110,4 @@ public class AppearancePreferencesFragment extends PreferencesFragment { return true; }); } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (data != null && requestCode == CODE_GROUPS) { - onGroupManagerResult(resultCode, data); - } - } - - private void onGroupManagerResult(int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - - HashSet groups = new HashSet<>(data.getStringArrayListExtra("groups")); - - for (VaultEntry entry : _vaultManager.getVault().getEntries()) { - if (!groups.contains(entry.getGroup())) { - entry.setGroup(null); - } - } - - saveAndBackupVault(); - } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/intro/IntroBaseActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/intro/IntroBaseActivity.java index 09d3ee85..41a54fb9 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/intro/IntroBaseActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/intro/IntroBaseActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.view.View; import android.widget.ImageButton; +import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -34,6 +35,7 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_intro); + getOnBackPressedDispatcher().addCallback(this, new BackPressHandler()); _slides = new ArrayList<>(); _state = new Bundle(); @@ -163,11 +165,6 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc return _state; } - @Override - public void onBackPressed() { - goToPreviousSlide(); - } - protected abstract void onDonePressed(); public void addSlide(Class type) { @@ -186,6 +183,17 @@ public abstract class IntroBaseActivity extends AegisActivity implements IntroAc } } + private class BackPressHandler extends OnBackPressedCallback { + public BackPressHandler() { + super(true); + } + + @Override + public void handleOnBackPressed() { + goToPreviousSlide(); + } + } + private class ScreenSlidePagerAdapter extends FragmentStateAdapter { public ScreenSlidePagerAdapter(FragmentManager fm) { super(fm, getLifecycle()); diff --git a/app/src/main/res/menu/menu_slots.xml b/app/src/main/res/menu/menu_groups.xml similarity index 82% rename from app/src/main/res/menu/menu_slots.xml rename to app/src/main/res/menu/menu_groups.xml index 4bbf9eba..5e614e22 100644 --- a/app/src/main/res/menu/menu_slots.xml +++ b/app/src/main/res/menu/menu_groups.xml @@ -2,7 +2,7 @@ + tools:context="com.beemdevelopment.aegis.ui.GroupManagerActivity">