mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-04 20:30:36 +00:00
Merge pull request #173 from michaelschattgen/feature-tapholdswipe
Overhaul entry interaction
This commit is contained in:
commit
c12c6ab107
16 changed files with 246 additions and 117 deletions
|
@ -3,8 +3,15 @@ package com.beemdevelopment.aegis.helpers;
|
|||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.ui.views.EntryAdapter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
private DatabaseEntry _selectedEntry;
|
||||
|
||||
private final ItemTouchHelperAdapter _adapter;
|
||||
private boolean _positionChanged = false;
|
||||
private boolean _isLongPressDragEnabled = true;
|
||||
|
@ -22,6 +29,10 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
_isLongPressDragEnabled = enabled;
|
||||
}
|
||||
|
||||
public void setSelectedEntry(DatabaseEntry entry) {
|
||||
_selectedEntry = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemViewSwipeEnabled() {
|
||||
return false;
|
||||
|
@ -30,7 +41,15 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
|
||||
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
|
||||
int swipeFlags = 0;
|
||||
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
EntryAdapter adapter = (EntryAdapter)recyclerView.getAdapter();
|
||||
if (adapter.getEntryAt(position) != _selectedEntry)
|
||||
{
|
||||
dragFlags = 0;
|
||||
}
|
||||
|
||||
return makeMovementFlags(dragFlags, swipeFlags);
|
||||
}
|
||||
|
||||
|
@ -56,4 +75,6 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||
_positionChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
||||
public class ThemeHelper {
|
||||
private ThemeHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static int getThemeColor(int attributeId, Resources.Theme currentTheme) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
currentTheme.resolveAttribute(attributeId, typedValue, true);
|
||||
@ColorInt int color = typedValue.data;
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
|
@ -11,6 +12,7 @@ import android.graphics.Rect;
|
|||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SubMenu;
|
||||
|
@ -18,6 +20,10 @@ import android.widget.LinearLayout;
|
|||
import android.widget.SearchView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
|
||||
import com.beemdevelopment.aegis.AegisApplication;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
|
@ -71,6 +77,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
private String _checkedGroup;
|
||||
private boolean _searchSubmitted;
|
||||
|
||||
private DatabaseEntry _selectedEntry;
|
||||
private ActionMode _actionMode;
|
||||
|
||||
private Menu _menu;
|
||||
private SearchView _searchView;
|
||||
private FloatingActionsMenu _fabMenu;
|
||||
|
@ -78,6 +87,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
|
||||
private FabScrollHelper _fabScrollHelper;
|
||||
|
||||
private ActionMode.Callback _actionModeCallbacks = new ActionModeCallbacks();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -483,43 +494,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private BottomSheetDialog createBottomSheet(final DatabaseEntry entry) {
|
||||
BottomSheetDialog dialog = new BottomSheetDialog(this);
|
||||
dialog.setContentView(R.layout.bottom_sheet_edit_entry);
|
||||
dialog.setCancelable(true);
|
||||
dialog.getWindow().setLayout(LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
dialog.show();
|
||||
|
||||
dialog.findViewById(R.id.copy_button).setOnClickListener(view -> {
|
||||
dialog.dismiss();
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("text/plain", entry.getInfo().getOtp());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, getString(R.string.code_copied), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
dialog.findViewById(R.id.delete_button).setOnClickListener(view -> {
|
||||
dialog.dismiss();
|
||||
Dialogs.showDeleteEntryDialog(this, (d, which) -> {
|
||||
deleteEntry(entry);
|
||||
// update the filter list if the group no longer exists
|
||||
if (entry.getGroup() != null) {
|
||||
if (!_db.getGroups().contains(entry.getGroup())) {
|
||||
updateGroupFilterMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dialog.findViewById(R.id.edit_button).setOnClickListener(view -> {
|
||||
dialog.dismiss();
|
||||
startEditProfileActivity(CODE_EDIT_ENTRY, entry, false);
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void deleteEntry(DatabaseEntry entry) {
|
||||
DatabaseEntry oldEntry = _db.removeEntry(entry);
|
||||
saveDatabase();
|
||||
|
@ -682,7 +656,29 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
|
||||
@Override
|
||||
public void onEntryClick(DatabaseEntry entry) {
|
||||
createBottomSheet(entry).show();
|
||||
if (_selectedEntry != null) {
|
||||
if (_selectedEntry == entry) {
|
||||
_actionMode.finish();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("text/plain", entry.getInfo().getOtp());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, getString(R.string.code_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongEntryClick(DatabaseEntry entry) {
|
||||
if (_selectedEntry != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedEntry = entry;
|
||||
_entryListView.setActionModeState(true, entry);
|
||||
_actionMode = this.startSupportActionMode(_actionModeCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -707,6 +703,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
|
||||
@Override
|
||||
public void onLocked() {
|
||||
if (_actionMode != null) {
|
||||
_actionMode.finish();
|
||||
}
|
||||
|
||||
_entryListView.clearEntries();
|
||||
_loaded = false;
|
||||
|
||||
|
@ -716,4 +716,50 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
|
||||
super.onLocked();
|
||||
}
|
||||
|
||||
private class ActionModeCallbacks implements ActionMode.Callback {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_action_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_edit:
|
||||
startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntry, false);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_delete:
|
||||
Dialogs.showDeleteEntryDialog(MainActivity.this, (d, which) -> {
|
||||
deleteEntry(_selectedEntry);
|
||||
|
||||
if (_selectedEntry.getGroup() != null) {
|
||||
if (!_db.getGroups().contains(_selectedEntry.getGroup())) {
|
||||
updateGroupFilterMenu();
|
||||
}
|
||||
}
|
||||
mode.finish();
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
_entryListView.setActionModeState(false, null);
|
||||
_selectedEntry = null;
|
||||
_actionMode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
private EntryListView _view;
|
||||
private List<DatabaseEntry> _entries;
|
||||
private List<DatabaseEntry> _shownEntries;
|
||||
private DatabaseEntry _selectedEntry;
|
||||
private boolean _showAccountName;
|
||||
private boolean _tapToReveal;
|
||||
private int _tapToRevealTime;
|
||||
|
@ -185,6 +186,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
if (_sortCategory == category) {
|
||||
return;
|
||||
}
|
||||
|
||||
_sortCategory = category;
|
||||
if (apply) {
|
||||
updateShownEntries();
|
||||
|
@ -274,6 +276,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
@Override
|
||||
public void onBindViewHolder(final EntryHolder holder, int position) {
|
||||
DatabaseEntry entry = _shownEntries.get(position);
|
||||
holder.setFocused(entry == _selectedEntry);
|
||||
|
||||
boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo;
|
||||
holder.setData(entry, _showAccountName, showProgress, _tapToReveal);
|
||||
holder.setTapToRevealTime(_tapToRevealTime);
|
||||
|
@ -283,7 +287,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
if (_tapToReveal && holder.isCodeHidden()) {
|
||||
if (_tapToReveal && holder.isCodeHidden() && _selectedEntry == null) {
|
||||
holder.revealCode();
|
||||
} else {
|
||||
_view.onEntryClick(_shownEntries.get(position));
|
||||
|
@ -294,6 +298,11 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
if (_selectedEntry == null) {
|
||||
setSelectedEntry(_shownEntries.get(position));
|
||||
holder.setFocused(true);
|
||||
}
|
||||
|
||||
return _view.onLongEntryClick(_shownEntries.get(position));
|
||||
}
|
||||
});
|
||||
|
@ -360,6 +369,18 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
|||
return period;
|
||||
}
|
||||
|
||||
public void setSelectedEntry(DatabaseEntry entry) {
|
||||
if (entry == null) {
|
||||
notifyItemChanged(_shownEntries.indexOf(_selectedEntry));
|
||||
}
|
||||
|
||||
_selectedEntry = entry;
|
||||
}
|
||||
|
||||
public boolean isDragAndDropAllowed() {
|
||||
return _sortCategory == SortCategory.CUSTOM && _groupFilter == null && _searchFilter == null;
|
||||
}
|
||||
|
||||
public boolean isPeriodUniform() {
|
||||
return getUniformPeriod() != -1;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.amulyakhare.textdrawable.TextDrawable;
|
|||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
import com.beemdevelopment.aegis.helpers.UiRefresher;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
|
@ -33,6 +34,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
private int _tapToRevealTime;
|
||||
|
||||
private PeriodProgressBar _progressBar;
|
||||
private View _view;
|
||||
|
||||
private UiRefresher _refresher;
|
||||
private Handler _hiddenHandler;
|
||||
|
@ -40,6 +42,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
public EntryHolder(final View view) {
|
||||
super(view);
|
||||
|
||||
_view = view.findViewById(R.id.rlCardEntry);
|
||||
|
||||
_profileName = view.findViewById(R.id.profile_account_name);
|
||||
_profileCode = view.findViewById(R.id.profile_code);
|
||||
_profileIssuer = view.findViewById(R.id.profile_issuer);
|
||||
|
@ -49,6 +53,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
_progressBar = view.findViewById(R.id.progressBar);
|
||||
int primaryColorId = view.getContext().getResources().getColor(R.color.colorPrimary);
|
||||
_progressBar.getProgressDrawable().setColorFilter(primaryColorId, PorterDuff.Mode.SRC_IN);
|
||||
_view.setBackground(_view.getContext().getResources().getDrawable(R.color.card_background));
|
||||
|
||||
_refresher = new UiRefresher(new UiRefresher.Listener() {
|
||||
@Override
|
||||
|
@ -135,6 +140,15 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
public void setFocused(boolean focused) {
|
||||
if (focused) {
|
||||
_view.setBackgroundColor(ThemeHelper.getThemeColor(R.attr.cardBackgroundFocused, _view.getContext().getTheme()));
|
||||
} else {
|
||||
_view.setBackgroundColor(ThemeHelper.getThemeColor(R.attr.cardBackground, _view.getContext().getTheme()));
|
||||
}
|
||||
_view.setSelected(focused);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
_refresher.destroy();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.LayoutAnimationController;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
|
@ -128,6 +129,12 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
}
|
||||
}
|
||||
|
||||
public void setActionModeState(boolean enabled, DatabaseEntry entry) {
|
||||
_touchCallback.setSelectedEntry(entry);
|
||||
_touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed());
|
||||
_adapter.setSelectedEntry(entry);
|
||||
}
|
||||
|
||||
public void setSortCategory(SortCategory sortCategory, boolean apply) {
|
||||
_touchCallback.setIsLongPressDragEnabled(sortCategory == SortCategory.CUSTOM);
|
||||
_adapter.setSortCategory(sortCategory, apply);
|
||||
|
@ -164,9 +171,9 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
_listener.onEntryClick(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongEntryClick(DatabaseEntry entry) {
|
||||
return false;
|
||||
_listener.onLongEntryClick(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -265,6 +272,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
|||
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);
|
||||
void onEntryDrop(DatabaseEntry entry);
|
||||
void onEntryChange(DatabaseEntry entry);
|
||||
void onLongEntryClick(DatabaseEntry entry);
|
||||
void onScroll(int dx, int dy);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue