Adds OnMoveCallback to the SwipeToDeleteCallback class.

Makes traits orderable.
This commit is contained in:
Tom Hicks
2021-06-26 23:21:28 -07:00
parent 1a16404948
commit 6a5278362c
11 changed files with 120 additions and 65 deletions

View File

@@ -7,23 +7,22 @@ import androidx.room.TypeConverters;
import com.majinnaibu.monstercards.data.MonsterDAO; import com.majinnaibu.monstercards.data.MonsterDAO;
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter; import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter; 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.SetOfLanguageConverter;
import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter; import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter;
import com.majinnaibu.monstercards.data.converters.SetOfStringConverter; 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.data.converters.UUIDConverter;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.MonsterFTS; import com.majinnaibu.monstercards.models.MonsterFTS;
@SuppressWarnings("unused")
@Database(entities = {Monster.class, MonsterFTS.class}, version = 3) @Database(entities = {Monster.class, MonsterFTS.class}, version = 3)
@TypeConverters({ @TypeConverters({
ArmorTypeConverter.class, ArmorTypeConverter.class,
ChallengeRatingConverter.class, ChallengeRatingConverter.class,
ListOfTraitsConverter.class,
SetOfLanguageConverter.class, SetOfLanguageConverter.class,
SetOfSkillConverter.class, SetOfSkillConverter.class,
SetOfStringConverter.class, SetOfStringConverter.class,
SetOfTraitConverter.class,
UUIDConverter.class, UUIDConverter.class,
}) })
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {

View File

@@ -7,20 +7,20 @@ import com.google.gson.reflect.TypeToken;
import com.majinnaibu.monstercards.models.Trait; import com.majinnaibu.monstercards.models.Trait;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashSet; import java.util.ArrayList;
import java.util.Set; import java.util.List;
public class SetOfTraitConverter { public class ListOfTraitsConverter {
@TypeConverter @TypeConverter
public static String fromSetOfTrait(Set<Trait> traits) { public static String fromListOfTraits(List<Trait> traits) {
Gson gson = new Gson(); Gson gson = new Gson();
return gson.toJson(traits); return gson.toJson(traits);
} }
@TypeConverter @TypeConverter
public static Set<Trait> setOfTraitFromString(String string) { public static List<Trait> listOfTraitsFromString(String string) {
Gson gson = new Gson(); Gson gson = new Gson();
Type setType = new TypeToken<HashSet<Trait>>() { Type setType = new TypeToken<ArrayList<Trait>>() {
}.getType(); }.getType();
return gson.fromJson(string, setType); return gson.fromJson(string, setType);
} }

View File

@@ -187,22 +187,22 @@ public class Monster {
public Set<Language> languages; public Set<Language> languages;
@ColumnInfo(name = "abilities", defaultValue = "[]") @ColumnInfo(name = "abilities", defaultValue = "[]")
public Set<Trait> abilities; public List<Trait> abilities;
@ColumnInfo(name = "actions", defaultValue = "[]") @ColumnInfo(name = "actions", defaultValue = "[]")
public Set<Trait> actions; public List<Trait> actions;
@ColumnInfo(name = "reactions", defaultValue = "[]") @ColumnInfo(name = "reactions", defaultValue = "[]")
public Set<Trait> reactions; public List<Trait> reactions;
@ColumnInfo(name = "lair_actions", defaultValue = "[]") @ColumnInfo(name = "lair_actions", defaultValue = "[]")
public Set<Trait> lairActions; public List<Trait> lairActions;
@ColumnInfo(name = "legendary_actions", defaultValue = "[]") @ColumnInfo(name = "legendary_actions", defaultValue = "[]")
public Set<Trait> legendaryActions; public List<Trait> legendaryActions;
@ColumnInfo(name = "regional_actions", defaultValue = "[]") @ColumnInfo(name = "regional_actions", defaultValue = "[]")
public Set<Trait> regionalActions; public List<Trait> regionalActions;
public Monster() { public Monster() {
id = UUID.randomUUID(); id = UUID.randomUUID();
@@ -258,12 +258,12 @@ public class Monster {
damageVulnerabilities = new HashSet<>(); damageVulnerabilities = new HashSet<>();
conditionImmunities = new HashSet<>(); conditionImmunities = new HashSet<>();
languages = new HashSet<>(); languages = new HashSet<>();
abilities = new HashSet<>(); abilities = new ArrayList<>();
actions = new HashSet<>(); actions = new ArrayList<>();
reactions = new HashSet<>(); reactions = new ArrayList<>();
lairActions = new HashSet<>(); lairActions = new ArrayList<>();
legendaryActions = new HashSet<>(); legendaryActions = new ArrayList<>();
regionalActions = new HashSet<>(); regionalActions = new ArrayList<>();
} }
public static int getAbilityModifierForScore(int score) { public static int getAbilityModifierForScore(int score) {

View File

@@ -75,11 +75,11 @@ public class EditLanguagesFragment extends MCFragment {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); recyclerView.addItemDecoration(dividerItemDecoration);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, position -> { ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> {
if (position > 0) { if (position > 0) {
mViewModel.removeLanguage(position - 1); mViewModel.removeLanguage(position - 1);
} }
})); }, null));
itemTouchHelper.attachToRecyclerView(recyclerView); itemTouchHelper.attachToRecyclerView(recyclerView);
} }

View File

@@ -992,12 +992,12 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
monster.damageVulnerabilities = new HashSet<>(mDamageVulnerabilities.getValue()); monster.damageVulnerabilities = new HashSet<>(mDamageVulnerabilities.getValue());
monster.conditionImmunities = new HashSet<>(mConditionImmunities.getValue()); monster.conditionImmunities = new HashSet<>(mConditionImmunities.getValue());
monster.languages = new HashSet<>(mLanguages.getValue()); monster.languages = new HashSet<>(mLanguages.getValue());
monster.abilities = new HashSet<>(mAbilities.getValue()); monster.abilities = new ArrayList<>(mAbilities.getValue());
monster.actions = new HashSet<>(mActions.getValue()); monster.actions = new ArrayList<>(mActions.getValue());
monster.reactions = new HashSet<>(mReactions.getValue()); monster.reactions = new ArrayList<>(mReactions.getValue());
monster.lairActions = new HashSet<>(mLairActions.getValue()); monster.lairActions = new ArrayList<>(mLairActions.getValue());
monster.legendaryActions = new HashSet<>(mLegendaryActions.getValue()); monster.legendaryActions = new ArrayList<>(mLegendaryActions.getValue());
monster.regionalActions = new HashSet<>(mRegionalActions.getValue()); monster.regionalActions = new ArrayList<>(mRegionalActions.getValue());
return monster; return monster;
} }
@@ -1176,12 +1176,30 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
@SuppressWarnings("SameParameterValue") public boolean moveTrait(TraitType type, int from, int to) {
private static class Helpers { switch (type) {
static String addStringToList(String newString, MutableLiveData<List<String>> strings) { case ABILITY:
return addItemToList(strings, newString, String::compareToIgnoreCase); 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> T addItemToList(MutableLiveData<List<T>> listData, T newItem) { static <T> T addItemToList(MutableLiveData<List<T>> listData, T newItem) {
return addItemToList(listData, newItem, null); return addItemToList(listData, newItem, null);
} }
@@ -1228,6 +1246,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
listData.setValue(newList); listData.setValue(newList);
} }
@SuppressWarnings("unused")
static <T> void replaceItemInList(MutableLiveData<List<T>> listData, int position, T newItem) { static <T> void replaceItemInList(MutableLiveData<List<T>> listData, int position, T newItem) {
replaceItemInList(listData, position, newItem, null); replaceItemInList(listData, position, newItem, null);
} }
@@ -1266,5 +1285,23 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
return value; return value;
} }
static <T> boolean moveItemInList(ChangeTrackedLiveData<List<T>> listData, int from, int to) {
List<T> oldList = listData.getValue();
if (oldList == null) {
oldList = new ArrayList<>();
}
ArrayList<T> 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;
}
} }
} }

View File

@@ -70,7 +70,7 @@ public class EditSkillsFragment extends MCFragment {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); 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); itemTouchHelper.attachToRecyclerView(recyclerView);
} }

View File

@@ -101,7 +101,7 @@ public class EditStringsFragment extends MCFragment {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); 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); itemTouchHelper.attachToRecyclerView(recyclerView);
} }

View File

@@ -35,6 +35,7 @@ public class EditTraitsFragment extends MCFragment {
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
private TraitType mTraitType; private TraitType mTraitType;
private EditTraitsRecyclerViewAdapter mAdapter;
@Override @Override
@@ -88,22 +89,21 @@ public class EditTraitsFragment extends MCFragment {
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
LiveData<List<Trait>> traitData = mViewModel.getTraits(mTraitType); LiveData<List<Trait>> traitData = mViewModel.getTraits(mTraitType);
if (traitData != null) { mAdapter = new EditTraitsRecyclerViewAdapter(trait -> {
traitData.observe(getViewLifecycleOwner(), traits -> {
EditTraitsRecyclerViewAdapter adapter = new EditTraitsRecyclerViewAdapter(traits, trait -> {
if (trait != null) { if (trait != null) {
navigateToEditTrait(trait); navigateToEditTrait(trait);
} else { } else {
Logger.logError("Can't navigate to EditTraitFragment with a null trait"); Logger.logError("Can't navigate to EditTraitFragment with a null trait");
} }
}); });
recyclerView.setAdapter(adapter); if (traitData != null) {
}); traitData.observe(getViewLifecycleOwner(), traits -> mAdapter.submitList(traits));
} }
recyclerView.setAdapter(mAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); 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); itemTouchHelper.attachToRecyclerView(recyclerView);
} }

View File

@@ -4,6 +4,9 @@ import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding; import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding;
@@ -11,14 +14,23 @@ import com.majinnaibu.monstercards.models.Trait;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> {
private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() {
public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter<EditTraitsRecyclerViewAdapter.ViewHolder> { @Override
private final List<Trait> mValues; 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; private final ItemCallback mOnClick;
public EditTraitsRecyclerViewAdapter(List<Trait> items, ItemCallback onClick) { protected EditTraitsRecyclerViewAdapter(ItemCallback onClick) {
mValues = items; super(DIFF_CALLBACK);
mOnClick = onClick; mOnClick = onClick;
} }
@@ -30,8 +42,8 @@ public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter<EditTrai
@Override @Override
public void onBindViewHolder(final ViewHolder holder, int position) { public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position); holder.mItem = getItem(position);
holder.mContentView.setText(mValues.get(position).name); holder.mContentView.setText(holder.mItem.name);
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(holder.mItem); mOnClick.onItemCallback(holder.mItem);
@@ -39,11 +51,6 @@ public class EditTraitsRecyclerViewAdapter extends RecyclerView.Adapter<EditTrai
}); });
} }
@Override
public int getItemCount() {
return mValues.size();
}
public interface ItemCallback { public interface ItemCallback {
void onItemCallback(Trait trait); void onItemCallback(Trait trait);
} }

View File

@@ -76,7 +76,7 @@ public class LibraryFragment extends MCFragment {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); recyclerView.addItemDecoration(dividerItemDecoration);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), adapter::deleteItem)); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), (position, direction) -> adapter.deleteItem(position), null));
itemTouchHelper.attachToRecyclerView(recyclerView); itemTouchHelper.attachToRecyclerView(recyclerView);
} }

View File

@@ -21,11 +21,14 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private final Drawable icon; private final Drawable icon;
private final ColorDrawable background; private final ColorDrawable background;
private final Paint clearPaint; private final Paint clearPaint;
private final OnDeleteCallback mOnDelete; private final OnSwipeCallback mOnDelete;
private final OnMoveCallback mOnMove;
private final Context mContext; 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; mOnDelete = onDelete;
mOnMove = onMove;
mContext = context; mContext = context;
icon = ContextCompat.getDrawable(mContext, R.drawable.ic_delete_white_36); icon = ContextCompat.getDrawable(mContext, R.drawable.ic_delete_white_36);
background = new ColorDrawable(context.getResources().getColor(R.color.red)); 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 viewHolder,
@NonNull RecyclerView.ViewHolder target @NonNull RecyclerView.ViewHolder target
) { ) {
if (mOnMove != null) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
return mOnMove.onMove(from, to);
}
return false; return false;
} }
@@ -46,7 +54,7 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (mOnDelete != null) { if (mOnDelete != null) {
int position = viewHolder.getAdapterPosition(); 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); super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
} }
public interface OnDeleteCallback { public interface OnSwipeCallback {
void onDelete(int position); void onSwipe(int position, int direction);
}
public interface OnMoveCallback {
boolean onMove(int from, int to);
} }
} }