From 6a5278362c6da025b8bf4ff51180303a22326001 Mon Sep 17 00:00:00 2001 From: Tom Hicks Date: Sat, 26 Jun 2021 23:21:28 -0700 Subject: [PATCH] Adds OnMoveCallback to the SwipeToDeleteCallback class. Makes traits orderable. --- .../majinnaibu/monstercards/AppDatabase.java | 5 +- ...verter.java => ListOfTraitsConverter.java} | 12 ++-- .../monstercards/models/Monster.java | 24 ++++---- .../ui/editmonster/EditLanguagesFragment.java | 4 +- .../ui/editmonster/EditMonsterViewModel.java | 57 +++++++++++++++---- .../ui/editmonster/EditSkillsFragment.java | 2 +- .../ui/editmonster/EditStringsFragment.java | 2 +- .../ui/editmonster/EditTraitsFragment.java | 22 +++---- .../EditTraitsRecyclerViewAdapter.java | 31 ++++++---- .../ui/library/LibraryFragment.java | 2 +- .../ui/shared/SwipeToDeleteCallback.java | 24 ++++++-- 11 files changed, 120 insertions(+), 65 deletions(-) rename app/src/main/java/com/majinnaibu/monstercards/data/converters/{SetOfTraitConverter.java => ListOfTraitsConverter.java} (62%) diff --git a/app/src/main/java/com/majinnaibu/monstercards/AppDatabase.java b/app/src/main/java/com/majinnaibu/monstercards/AppDatabase.java index 14d6d6e..7b27aa6 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/AppDatabase.java +++ b/app/src/main/java/com/majinnaibu/monstercards/AppDatabase.java @@ -7,23 +7,22 @@ import androidx.room.TypeConverters; import com.majinnaibu.monstercards.data.MonsterDAO; import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter; import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter; +import com.majinnaibu.monstercards.data.converters.ListOfTraitsConverter; import com.majinnaibu.monstercards.data.converters.SetOfLanguageConverter; import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter; import com.majinnaibu.monstercards.data.converters.SetOfStringConverter; -import com.majinnaibu.monstercards.data.converters.SetOfTraitConverter; import com.majinnaibu.monstercards.data.converters.UUIDConverter; import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.MonsterFTS; -@SuppressWarnings("unused") @Database(entities = {Monster.class, MonsterFTS.class}, version = 3) @TypeConverters({ ArmorTypeConverter.class, ChallengeRatingConverter.class, + ListOfTraitsConverter.class, SetOfLanguageConverter.class, SetOfSkillConverter.class, SetOfStringConverter.class, - SetOfTraitConverter.class, UUIDConverter.class, }) public abstract class AppDatabase extends RoomDatabase { diff --git a/app/src/main/java/com/majinnaibu/monstercards/data/converters/SetOfTraitConverter.java b/app/src/main/java/com/majinnaibu/monstercards/data/converters/ListOfTraitsConverter.java similarity index 62% rename from app/src/main/java/com/majinnaibu/monstercards/data/converters/SetOfTraitConverter.java rename to app/src/main/java/com/majinnaibu/monstercards/data/converters/ListOfTraitsConverter.java index 029b102..7a0e2ef 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/data/converters/SetOfTraitConverter.java +++ b/app/src/main/java/com/majinnaibu/monstercards/data/converters/ListOfTraitsConverter.java @@ -7,20 +7,20 @@ import com.google.gson.reflect.TypeToken; import com.majinnaibu.monstercards.models.Trait; import java.lang.reflect.Type; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; -public class SetOfTraitConverter { +public class ListOfTraitsConverter { @TypeConverter - public static String fromSetOfTrait(Set traits) { + public static String fromListOfTraits(List traits) { Gson gson = new Gson(); return gson.toJson(traits); } @TypeConverter - public static Set setOfTraitFromString(String string) { + public static List listOfTraitsFromString(String string) { Gson gson = new Gson(); - Type setType = new TypeToken>() { + Type setType = new TypeToken>() { }.getType(); return gson.fromJson(string, setType); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java b/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java index e39bc82..6478458 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java +++ b/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java @@ -187,22 +187,22 @@ public class Monster { public Set languages; @ColumnInfo(name = "abilities", defaultValue = "[]") - public Set abilities; + public List abilities; @ColumnInfo(name = "actions", defaultValue = "[]") - public Set actions; + public List actions; @ColumnInfo(name = "reactions", defaultValue = "[]") - public Set reactions; + public List reactions; @ColumnInfo(name = "lair_actions", defaultValue = "[]") - public Set lairActions; + public List lairActions; @ColumnInfo(name = "legendary_actions", defaultValue = "[]") - public Set legendaryActions; + public List legendaryActions; @ColumnInfo(name = "regional_actions", defaultValue = "[]") - public Set regionalActions; + public List regionalActions; public Monster() { id = UUID.randomUUID(); @@ -258,12 +258,12 @@ public class Monster { damageVulnerabilities = new HashSet<>(); conditionImmunities = new HashSet<>(); languages = new HashSet<>(); - abilities = new HashSet<>(); - actions = new HashSet<>(); - reactions = new HashSet<>(); - lairActions = new HashSet<>(); - legendaryActions = new HashSet<>(); - regionalActions = new HashSet<>(); + abilities = new ArrayList<>(); + actions = new ArrayList<>(); + reactions = new ArrayList<>(); + lairActions = new ArrayList<>(); + legendaryActions = new ArrayList<>(); + regionalActions = new ArrayList<>(); } public static int getAbilityModifierForScore(int score) { diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditLanguagesFragment.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditLanguagesFragment.java index d22e66d..8714a9f 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditLanguagesFragment.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditLanguagesFragment.java @@ -75,11 +75,11 @@ public class EditLanguagesFragment extends MCFragment { DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); recyclerView.addItemDecoration(dividerItemDecoration); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, position -> { + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> { if (position > 0) { mViewModel.removeLanguage(position - 1); } - })); + }, null)); itemTouchHelper.attachToRecyclerView(recyclerView); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java index 47a727c..5353206 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java @@ -992,12 +992,12 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel { monster.damageVulnerabilities = new HashSet<>(mDamageVulnerabilities.getValue()); monster.conditionImmunities = new HashSet<>(mConditionImmunities.getValue()); monster.languages = new HashSet<>(mLanguages.getValue()); - monster.abilities = new HashSet<>(mAbilities.getValue()); - monster.actions = new HashSet<>(mActions.getValue()); - monster.reactions = new HashSet<>(mReactions.getValue()); - monster.lairActions = new HashSet<>(mLairActions.getValue()); - monster.legendaryActions = new HashSet<>(mLegendaryActions.getValue()); - monster.regionalActions = new HashSet<>(mRegionalActions.getValue()); + monster.abilities = new ArrayList<>(mAbilities.getValue()); + monster.actions = new ArrayList<>(mActions.getValue()); + monster.reactions = new ArrayList<>(mReactions.getValue()); + monster.lairActions = new ArrayList<>(mLairActions.getValue()); + monster.legendaryActions = new ArrayList<>(mLegendaryActions.getValue()); + monster.regionalActions = new ArrayList<>(mRegionalActions.getValue()); return monster; } @@ -1176,12 +1176,30 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel { } } - @SuppressWarnings("SameParameterValue") - private static class Helpers { - static String addStringToList(String newString, MutableLiveData> strings) { - return addItemToList(strings, newString, String::compareToIgnoreCase); + public boolean moveTrait(TraitType type, int from, int to) { + switch (type) { + case ABILITY: + return Helpers.moveItemInList(mAbilities, from, to); + case ACTION: + return Helpers.moveItemInList(mActions, from, to); + case LAIR_ACTION: + return Helpers.moveItemInList(mLairActions, from, to); + case LEGENDARY_ACTION: + return Helpers.moveItemInList(mLegendaryActions, from, to); + case REACTIONS: + return Helpers.moveItemInList(mReactions, from, to); + case REGIONAL_ACTION: + return Helpers.moveItemInList(mRegionalActions, from, to); + default: + Logger.logUnimplementedFeature(String.format("Unrecognized TraitType: %s", type)); + return false; } + + } + + @SuppressWarnings("SameParameterValue") + private static class Helpers { static T addItemToList(MutableLiveData> listData, T newItem) { return addItemToList(listData, newItem, null); } @@ -1228,6 +1246,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel { listData.setValue(newList); } + @SuppressWarnings("unused") static void replaceItemInList(MutableLiveData> listData, int position, T newItem) { replaceItemInList(listData, position, newItem, null); } @@ -1266,5 +1285,23 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel { } return value; } + + static boolean moveItemInList(ChangeTrackedLiveData> listData, int from, int to) { + List oldList = listData.getValue(); + if (oldList == null) { + oldList = new ArrayList<>(); + } + ArrayList newList = new ArrayList<>(oldList); + T item = oldList.get(from); + if (from > to) { + from = from + 1; + } else if (to > from) { + to = to + 1; + } + newList.add(to, item); + newList.remove(from); + listData.setValue(newList); + return true; + } } } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditSkillsFragment.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditSkillsFragment.java index 269ca30..f14389d 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditSkillsFragment.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditSkillsFragment.java @@ -70,7 +70,7 @@ public class EditSkillsFragment extends MCFragment { DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); recyclerView.addItemDecoration(dividerItemDecoration); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, mViewModel::removeSkill)); + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeSkill(position), null)); itemTouchHelper.attachToRecyclerView(recyclerView); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java index 33927b9..18966aa 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java @@ -101,7 +101,7 @@ public class EditStringsFragment extends MCFragment { DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); recyclerView.addItemDecoration(dividerItemDecoration); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, position -> mViewModel.removeString(mStringType, position))); + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeString(mStringType, position), null)); itemTouchHelper.attachToRecyclerView(recyclerView); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsFragment.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsFragment.java index 4ff2c86..4b09d00 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsFragment.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsFragment.java @@ -35,6 +35,7 @@ public class EditTraitsFragment extends MCFragment { private EditMonsterViewModel mViewModel; private ViewHolder mHolder; private TraitType mTraitType; + private EditTraitsRecyclerViewAdapter mAdapter; @Override @@ -88,22 +89,21 @@ public class EditTraitsFragment extends MCFragment { recyclerView.setLayoutManager(layoutManager); LiveData> traitData = mViewModel.getTraits(mTraitType); + mAdapter = new EditTraitsRecyclerViewAdapter(trait -> { + if (trait != null) { + navigateToEditTrait(trait); + } else { + Logger.logError("Can't navigate to EditTraitFragment with a null trait"); + } + }); if (traitData != null) { - traitData.observe(getViewLifecycleOwner(), traits -> { - EditTraitsRecyclerViewAdapter adapter = new EditTraitsRecyclerViewAdapter(traits, trait -> { - if (trait != null) { - navigateToEditTrait(trait); - } else { - Logger.logError("Can't navigate to EditTraitFragment with a null trait"); - } - }); - recyclerView.setAdapter(adapter); - }); + traitData.observe(getViewLifecycleOwner(), traits -> mAdapter.submitList(traits)); } + recyclerView.setAdapter(mAdapter); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); recyclerView.addItemDecoration(dividerItemDecoration); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, position -> mViewModel.removeTrait(mTraitType, position))); + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeTrait(mTraitType, position), (from, to) -> mViewModel.moveTrait(mTraitType, from, to))); itemTouchHelper.attachToRecyclerView(recyclerView); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsRecyclerViewAdapter.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsRecyclerViewAdapter.java index 175fa35..b5362c1 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsRecyclerViewAdapter.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditTraitsRecyclerViewAdapter.java @@ -4,6 +4,9 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding; @@ -11,14 +14,23 @@ import com.majinnaibu.monstercards.models.Trait; import org.jetbrains.annotations.NotNull; -import java.util.List; +public class EditTraitsRecyclerViewAdapter extends ListAdapter { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { -public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter { - private final List mValues; + @Override + public boolean areItemsTheSame(@NonNull @NotNull Trait oldItem, @NonNull @NotNull Trait newItem) { + return oldItem.equals(newItem); + } + + @Override + public boolean areContentsTheSame(@NonNull @NotNull Trait oldItem, @NonNull @NotNull Trait newItem) { + return oldItem.equals(newItem); + } + }; private final ItemCallback mOnClick; - public EditTraitsRecyclerViewAdapter(List items, ItemCallback onClick) { - mValues = items; + protected EditTraitsRecyclerViewAdapter(ItemCallback onClick) { + super(DIFF_CALLBACK); mOnClick = onClick; } @@ -30,8 +42,8 @@ public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter { if (mOnClick != null) { mOnClick.onItemCallback(holder.mItem); @@ -39,11 +51,6 @@ public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter adapter.deleteItem(position), null)); itemTouchHelper.attachToRecyclerView(recyclerView); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/shared/SwipeToDeleteCallback.java b/app/src/main/java/com/majinnaibu/monstercards/ui/shared/SwipeToDeleteCallback.java index d8bd51a..c333a44 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/shared/SwipeToDeleteCallback.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/shared/SwipeToDeleteCallback.java @@ -21,11 +21,14 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback { private final Drawable icon; private final ColorDrawable background; private final Paint clearPaint; - private final OnDeleteCallback mOnDelete; + private final OnSwipeCallback mOnDelete; + private final OnMoveCallback mOnMove; private final Context mContext; - public SwipeToDeleteCallback(Context context, OnDeleteCallback onDelete) { - super(0, ItemTouchHelper.LEFT); + + public SwipeToDeleteCallback(Context context, OnSwipeCallback onDelete, OnMoveCallback onMove) { + super(onMove == null ? 0 : ItemTouchHelper.UP | ItemTouchHelper.DOWN, onDelete == null ? 0 : ItemTouchHelper.LEFT); mOnDelete = onDelete; + mOnMove = onMove; mContext = context; icon = ContextCompat.getDrawable(mContext, R.drawable.ic_delete_white_36); background = new ColorDrawable(context.getResources().getColor(R.color.red)); @@ -39,6 +42,11 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback { @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target ) { + if (mOnMove != null) { + int from = viewHolder.getAdapterPosition(); + int to = target.getAdapterPosition(); + return mOnMove.onMove(from, to); + } return false; } @@ -46,7 +54,7 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback { public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { if (mOnDelete != null) { int position = viewHolder.getAdapterPosition(); - mOnDelete.onDelete(position); + mOnDelete.onSwipe(position, direction); } } @@ -80,7 +88,11 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } - public interface OnDeleteCallback { - void onDelete(int position); + public interface OnSwipeCallback { + void onSwipe(int position, int direction); + } + + public interface OnMoveCallback { + boolean onMove(int from, int to); } }