Adds removing a monster from the dashboard via long press.

This commit is contained in:
2026-09-20 16:29:13 -07:00
parent c06b53ed6b
commit 0cbd03da69
18 changed files with 336 additions and 81 deletions

View File

@@ -21,8 +21,6 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import android.widget.Toast;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.gson.Gson;
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
@@ -31,6 +29,7 @@ import com.majinnaibu.monstercards.importers.BinderImporter;
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
import com.majinnaibu.monstercards.init.AppCenterInitializer;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.ToastHelper;
import java.io.BufferedReader;
import java.io.IOException;
@@ -141,24 +140,24 @@ public class MainActivity extends AppCompatActivity {
public void importMonsterFromInputAndNavigate(@NonNull String input) {
BinderImporter binderImporter = new BinderImporter();
if (binderImporter.canImport(input)) {
Toast.makeText(this, R.string.toast_importing_url, Toast.LENGTH_SHORT).show();
ToastHelper.showShort(this, R.string.toast_importing_url);
Single.fromCallable(() -> binderImporter.parse(input))
.flatMapCompletable(binder -> ((MonsterCardsApplication) getApplication()).getMonsterRepository().importBinder(binder))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
Toast.makeText(this, R.string.snackbar_import_binder_success, Toast.LENGTH_LONG).show();
ToastHelper.showLong(this, R.string.snackbar_import_binder_success);
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
NavController navController = navHostFragment.getNavController();
navController.navigate(R.id.navigation_library);
}, throwable -> {
Logger.logError("Failed to import binder from input", throwable);
Toast.makeText(this, R.string.failed_to_import_url, Toast.LENGTH_LONG).show();
ToastHelper.showLong(this, R.string.failed_to_import_url);
});
return;
}
Toast.makeText(this, R.string.toast_importing_url, Toast.LENGTH_SHORT).show();
ToastHelper.showShort(this, R.string.toast_importing_url);
Single.fromCallable(() -> MonsterImportHelper.fromJSON(input))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -170,7 +169,7 @@ public class MainActivity extends AppCompatActivity {
navController.navigate(navAction);
}, throwable -> {
Logger.logError("Failed to import monster from input", throwable);
Toast.makeText(this, R.string.failed_to_import_url, Toast.LENGTH_LONG).show();
ToastHelper.showLong(this, R.string.failed_to_import_url);
});
}

View File

@@ -3,9 +3,11 @@ package com.majinnaibu.monstercards.data;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import com.majinnaibu.monstercards.models.DashboardMonster;
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
import com.majinnaibu.monstercards.models.Monster;
import java.util.List;
@@ -19,6 +21,10 @@ public interface DashboardDAO {
@Query("SELECT monsters.* FROM monsters INNER JOIN dashboard_monsters ON monsters.id = dashboard_monsters.monster_id ORDER BY dashboard_monsters.ordinal ASC, dashboard_monsters.id ASC")
Flowable<List<Monster>> getDashboardMonsters();
@Transaction
@Query("SELECT * FROM dashboard_monsters ORDER BY ordinal ASC, id ASC")
Flowable<List<DashboardMonsterWithMonster>> getDashboardMonstersWithMonster();
@Query("SELECT * FROM dashboard_monsters ORDER BY ordinal ASC, id ASC")
Flowable<List<DashboardMonster>> getDashboardMonsterEntries();
@@ -28,9 +34,12 @@ public interface DashboardDAO {
@Query("DELETE FROM dashboard_monsters WHERE id = :id")
Completable removeDashboardMonsterById(long id);
@Query("DELETE FROM dashboard_monsters WHERE monster_id = :monsterId")
@Query("DELETE FROM dashboard_monsters WHERE id = (SELECT id FROM dashboard_monsters WHERE monster_id = :monsterId LIMIT 1)")
Completable removeMonsterFromDashboard(String monsterId);
@Query("DELETE FROM dashboard_monsters WHERE monster_id = :monsterId")
Completable removeAllMonstersFromDashboard(String monsterId);
@Query("DELETE FROM dashboard_monsters")
Completable clearDashboard();

View File

@@ -208,6 +208,13 @@ public class MonsterRepository {
.observeOn(AndroidSchedulers.mainThread());
}
public Flowable<List<com.majinnaibu.monstercards.models.DashboardMonsterWithMonster>> getDashboardMonstersWithMonster() {
return m_db.dashboardDAO()
.getDashboardMonstersWithMonster()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Flowable<List<DashboardMonster>> getDashboardMonsterEntries() {
return m_db.dashboardDAO()
.getDashboardMonsterEntries()

View File

@@ -0,0 +1,23 @@
package com.majinnaibu.monstercards.models;
import androidx.room.Embedded;
import androidx.room.Relation;
public class DashboardMonsterWithMonster {
@Embedded
public DashboardMonster dashboardEntry;
@Relation(
parentColumn = "monster_id",
entityColumn = "id"
)
public Monster monster;
public DashboardMonsterWithMonster() {
}
public DashboardMonsterWithMonster(DashboardMonster dashboardEntry, Monster monster) {
this.dashboardEntry = dashboardEntry;
this.monster = monster;
}
}

View File

@@ -21,15 +21,18 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.DashboardMonster;
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.dashboard.DashboardRecyclerViewAdapter;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -80,7 +83,15 @@ public class CollectionDetailFragment extends MCFragment {
mViewModel.getMonsters().observe(getViewLifecycleOwner(), monsters -> {
if (mAdapter != null) {
mAdapter.submitList(monsters);
List<DashboardMonsterWithMonster> items = new ArrayList<>();
if (monsters != null) {
for (int i = 0; i < monsters.size(); i++) {
DashboardMonster dm = new DashboardMonster(monsters.get(i).id, i);
dm.id = i + 1;
items.add(new DashboardMonsterWithMonster(dm, monsters.get(i)));
}
}
mAdapter.submitList(items);
}
});
@@ -113,9 +124,9 @@ public class CollectionDetailFragment extends MCFragment {
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
recyclerView.setLayoutManager(layoutManager);
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
if (monster != null) {
navigateToMonsterDetail(monster.id);
mAdapter = new DashboardRecyclerViewAdapter(item -> {
if (item != null && item.monster != null) {
navigateToMonsterDetail(item.monster.id);
} else {
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
}
@@ -133,7 +144,7 @@ public class CollectionDetailFragment extends MCFragment {
if (monsters.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
}
return;
}
@@ -162,11 +173,7 @@ public class CollectionDetailFragment extends MCFragment {
if (view != null) {
Collection collection = mViewModel.getCollection().getValue();
String collectionName = collection != null ? collection.name : "";
Snackbar.make(
view,
getString(R.string.snackbar_monster_added_to_collection, monster.name, collectionName),
Snackbar.LENGTH_LONG)
.show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_monster_added_to_collection, monster.name, collectionName));
}
}, Logger::logError));
}
@@ -219,7 +226,7 @@ public class CollectionDetailFragment extends MCFragment {
if (view != null) {
Collection collection = mViewModel.getCollection().getValue();
String collectionName = collection != null ? collection.name : "";
Snackbar.make(view, getString(R.string.snackbar_collection_added_to_dashboard, collectionName), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_collection_added_to_dashboard, collectionName));
}
}, Logger::logError));
}

View File

@@ -20,13 +20,13 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.UUID;
@@ -150,10 +150,7 @@ public class CollectionsFragment extends MCFragment {
public void onComplete() {
View view = getView();
if (view != null) {
Snackbar.make(
view,
getString(R.string.snackbar_collection_created, collection.name),
Snackbar.LENGTH_LONG)
SnackbarHelper.makeLong(view, getString(R.string.snackbar_collection_created, collection.name))
.setAction("View", v -> navigateToCollectionDetail(collection.id))
.show();
}
@@ -164,7 +161,7 @@ public class CollectionsFragment extends MCFragment {
Logger.logError("Error creating collection", e);
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_collection), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_collection));
}
}
});

View File

@@ -21,13 +21,13 @@ import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.ArrayList;
import java.util.List;
@@ -69,7 +69,7 @@ public class DashboardFragment extends MCFragment {
private void loadDashboardMonsters() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getDashboardMonsters()
mDisposables.add(repository.getDashboardMonstersWithMonster()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> mViewModel.setMonsters(monsters), Logger::logError));
@@ -82,14 +82,21 @@ public class DashboardFragment extends MCFragment {
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
recyclerView.setLayoutManager(layoutManager);
LiveData<List<Monster>> monsterData = mViewModel.getMonsters();
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
if (monster != null) {
navigateToMonsterDetail(monster);
} else {
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
}
});
LiveData<List<com.majinnaibu.monstercards.models.DashboardMonsterWithMonster>> monsterData = mViewModel.getMonsters();
mAdapter = new DashboardRecyclerViewAdapter(
item -> {
if (item != null && item.monster != null) {
navigateToMonsterDetail(item.monster);
} else {
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
}
},
item -> {
if (item != null) {
showCardOptionsMenu(item);
}
}
);
if (monsterData != null) {
monsterData.observe(getViewLifecycleOwner(), monsters -> {
mAdapter.submitList(monsters);
@@ -113,9 +120,9 @@ public class DashboardFragment extends MCFragment {
int fromPos = viewHolder.getAdapterPosition();
int toPos = target.getAdapterPosition();
if (fromPos != RecyclerView.NO_POSITION && toPos != RecyclerView.NO_POSITION) {
List<Monster> currentList = new ArrayList<>(mAdapter.getCurrentList());
List<com.majinnaibu.monstercards.models.DashboardMonsterWithMonster> currentList = new ArrayList<>(mAdapter.getCurrentList());
if (fromPos < currentList.size() && toPos < currentList.size()) {
Monster moved = currentList.remove(fromPos);
com.majinnaibu.monstercards.models.DashboardMonsterWithMonster moved = currentList.remove(fromPos);
currentList.add(toPos, moved);
mAdapter.submitList(currentList);
}
@@ -127,10 +134,10 @@ public class DashboardFragment extends MCFragment {
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
List<Monster> currentList = mAdapter.getCurrentList();
List<com.majinnaibu.monstercards.models.DashboardMonsterWithMonster> currentList = mAdapter.getCurrentList();
if (position < currentList.size()) {
Monster monster = currentList.get(position);
removeMonsterFromDashboard(monster);
com.majinnaibu.monstercards.models.DashboardMonsterWithMonster item = currentList.get(position);
removeDashboardMonsterEntry(item);
}
}
}
@@ -142,6 +149,7 @@ public class DashboardFragment extends MCFragment {
String[] options = new String[]{
getString(R.string.action_add_single_monster),
getString(R.string.action_add_collection_option),
getString(R.string.action_remove_single_monster),
getString(R.string.action_clear_dashboard)
};
new AlertDialog.Builder(requireContext())
@@ -152,6 +160,8 @@ public class DashboardFragment extends MCFragment {
} else if (which == 1) {
showAddCollectionPicker();
} else if (which == 2) {
showRemoveMonsterPicker();
} else if (which == 3) {
clearDashboard();
}
})
@@ -159,6 +169,68 @@ public class DashboardFragment extends MCFragment {
.show();
}
private void showRemoveMonsterPicker() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getDashboardMonsters()
.firstOrError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> {
if (monsters.isEmpty()) {
View view = getView();
if (view != null) {
SnackbarHelper.showLong(view, getString(R.string.snackbar_dashboard_already_empty));
}
return;
}
String[] names = new String[monsters.size()];
for (int i = 0; i < monsters.size(); i++) {
names[i] = monsters.get(i).name;
}
new AlertDialog.Builder(requireContext())
.setTitle(R.string.action_remove_single_monster)
.setItems(names, (dialog, which) -> {
Monster selected = monsters.get(which);
removeMonsterFromDashboard(selected);
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}, Logger::logError));
}
private void showCardOptionsMenu(@NonNull com.majinnaibu.monstercards.models.DashboardMonsterWithMonster item) {
String[] options = new String[]{
getString(R.string.action_view_details),
getString(R.string.action_remove_from_dashboard)
};
new AlertDialog.Builder(requireContext())
.setTitle(item.monster.name)
.setItems(options, (dialog, which) -> {
if (which == 0) {
navigateToMonsterDetail(item.monster);
} else if (which == 1) {
removeDashboardMonsterEntry(item);
}
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}
private void removeDashboardMonsterEntry(@NonNull com.majinnaibu.monstercards.models.DashboardMonsterWithMonster item) {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.removeDashboardMonsterById(item.dashboardEntry.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
SnackbarHelper.makeLong(view, getString(R.string.snackbar_removed_from_dashboard, item.monster.name))
.setAction(R.string.action_undo, v -> addMonsterToDashboard(item.monster))
.show();
}
}, Logger::logError));
}
private void showAddMonsterPicker() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getMonsters()
@@ -169,7 +241,7 @@ public class DashboardFragment extends MCFragment {
if (monsters.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.no_monsters_available), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.no_monsters_available));
}
return;
}
@@ -198,7 +270,7 @@ public class DashboardFragment extends MCFragment {
if (collections.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.no_collections_available), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.no_collections_available));
}
return;
}
@@ -225,7 +297,7 @@ public class DashboardFragment extends MCFragment {
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_added_to_dashboard, monster.name), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_added_to_dashboard, monster.name));
}
}, Logger::logError));
}
@@ -238,7 +310,7 @@ public class DashboardFragment extends MCFragment {
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_collection_added_to_dashboard, collection.name), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_collection_added_to_dashboard, collection.name));
}
}, Logger::logError));
}
@@ -248,7 +320,14 @@ public class DashboardFragment extends MCFragment {
mDisposables.add(repository.removeMonsterFromDashboard(monster.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, Logger::logError));
.subscribe(() -> {
View view = getView();
if (view != null) {
SnackbarHelper.makeLong(view, getString(R.string.snackbar_removed_from_dashboard, monster.name))
.setAction(R.string.action_undo, v -> addMonsterToDashboard(monster))
.show();
}
}, Logger::logError));
}
private void clearDashboard() {
@@ -259,7 +338,7 @@ public class DashboardFragment extends MCFragment {
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_dashboard_cleared), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_dashboard_cleared));
}
}, Logger::logError));
}
@@ -283,6 +362,9 @@ public class DashboardFragment extends MCFragment {
} else if (item.getItemId() == R.id.menu_action_add_collection_option) {
showAddCollectionPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_remove_single_monster) {
showRemoveMonsterPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_clear_dashboard) {
clearDashboard();
return true;

View File

@@ -18,29 +18,44 @@ import com.majinnaibu.monstercards.data.enums.ChallengeRating;
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
import com.majinnaibu.monstercards.databinding.CardMonsterBinding;
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.Trait;
import com.majinnaibu.monstercards.utils.Logger;
import java.util.Locale;
public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, DashboardRecyclerViewAdapter.ViewHolder> {
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
public class DashboardRecyclerViewAdapter extends ListAdapter<DashboardMonsterWithMonster, DashboardRecyclerViewAdapter.ViewHolder> {
private static final DiffUtil.ItemCallback<DashboardMonsterWithMonster> DIFF_CALLBACK = new DiffUtil.ItemCallback<DashboardMonsterWithMonster>() {
@Override
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return oldItem.id.equals(newItem.id);
public boolean areItemsTheSame(@NonNull DashboardMonsterWithMonster oldItem, @NonNull DashboardMonsterWithMonster newItem) {
if (oldItem.dashboardEntry != null && newItem.dashboardEntry != null) {
return oldItem.dashboardEntry.id == newItem.dashboardEntry.id;
}
return oldItem.monster.id.equals(newItem.monster.id);
}
@Override
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return oldItem.equals(newItem);
public boolean areContentsTheSame(@NonNull DashboardMonsterWithMonster oldItem, @NonNull DashboardMonsterWithMonster newItem) {
if (oldItem.dashboardEntry != null && newItem.dashboardEntry != null) {
return oldItem.dashboardEntry.id == newItem.dashboardEntry.id
&& oldItem.dashboardEntry.ordinal == newItem.dashboardEntry.ordinal
&& (oldItem.monster != null && oldItem.monster.equals(newItem.monster));
}
return (oldItem.monster != null && oldItem.monster.equals(newItem.monster));
}
};
private final ItemCallback mOnClick;
private final ItemCallback mOnLongClick;
public DashboardRecyclerViewAdapter(ItemCallback onClick) {
public DashboardRecyclerViewAdapter(ItemCallback onClick, ItemCallback onLongClick) {
super(DIFF_CALLBACK);
mOnClick = onClick;
mOnLongClick = onLongClick;
}
public DashboardRecyclerViewAdapter(ItemCallback onClick) {
this(onClick, null);
}
@NonNull
@@ -52,7 +67,9 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Logger.logUnimplementedMethod();
Monster monster = getItem(position);
DashboardMonsterWithMonster item = getItem(position);
Monster monster = item.monster;
holder.item = item;
holder.monster = monster;
holder.name.setText(monster.name);
holder.meta.setText(monster.getMeta());
@@ -120,16 +137,25 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) {
mOnClick.onItemCallback(holder.monster);
mOnClick.onItemCallback(holder.item);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (mOnLongClick != null) {
mOnLongClick.onItemCallback(holder.item);
return true;
}
return false;
});
}
public interface ItemCallback {
void onItemCallback(Monster monster);
void onItemCallback(DashboardMonsterWithMonster item);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public DashboardMonsterWithMonster item;
public final TextView name;
public final TextView meta;
public final View action1Group;

View File

@@ -4,23 +4,23 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.DashboardMonsterWithMonster;
import java.util.ArrayList;
import java.util.List;
public class DashboardViewModel extends ViewModel {
private final MutableLiveData<List<Monster>> mMonsters;
private final MutableLiveData<List<DashboardMonsterWithMonster>> mMonsters;
public DashboardViewModel() {
mMonsters = new MutableLiveData<>(new ArrayList<>());
}
public LiveData<List<Monster>> getMonsters() {
public LiveData<List<DashboardMonsterWithMonster>> getMonsters() {
return mMonsters;
}
public void setMonsters(List<Monster> monsters) {
public void setMonsters(List<DashboardMonsterWithMonster> monsters) {
mMonsters.setValue(monsters);
}
}

View File

@@ -18,7 +18,6 @@ import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.data.enums.StringType;
@@ -26,6 +25,7 @@ import com.majinnaibu.monstercards.data.enums.TraitType;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.ArrayList;
import java.util.List;
@@ -124,8 +124,7 @@ public class EditMonsterFragment extends MCFragment {
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
Logger.logError("Error saving monster", e);
assert view != null;
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
}
});
});

View File

@@ -24,7 +24,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.MainActivity;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
@@ -35,6 +34,7 @@ import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentDirections;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.UUID;
@@ -206,7 +206,7 @@ public class LibraryFragment extends MCFragment {
Logger.logError("Error creating monster", e);
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
}
}
}));

View File

@@ -31,7 +31,6 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
@@ -40,6 +39,7 @@ import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.List;
import java.util.UUID;
@@ -252,7 +252,7 @@ public class MonsterDetailFragment extends MCFragment {
Logger.logError("Failed to share monster", e);
View view = getView();
if (view != null) {
Snackbar.make(view, R.string.failed_to_share_monster, Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, R.string.failed_to_share_monster);
}
}
}
@@ -270,7 +270,7 @@ public class MonsterDetailFragment extends MCFragment {
View view = getView();
if (view != null) {
String monsterName = mViewModel.getName().getValue();
Snackbar.make(view, getString(R.string.snackbar_added_to_dashboard, monsterName), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_added_to_dashboard, monsterName));
}
}, Logger::logError);
}
@@ -291,7 +291,7 @@ public class MonsterDetailFragment extends MCFragment {
if (collections.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.no_collections_available), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, getString(R.string.no_collections_available));
}
dispose();
return;
@@ -330,11 +330,7 @@ public class MonsterDetailFragment extends MCFragment {
View view = getView();
if (view != null) {
String monsterName = mViewModel.getName().getValue();
Snackbar.make(
view,
getString(R.string.snackbar_monster_added_to_collection, monsterName, collection.name),
Snackbar.LENGTH_LONG)
.show();
SnackbarHelper.showLong(view, getString(R.string.snackbar_monster_added_to_collection, monsterName, collection.name));
}
}

View File

@@ -23,7 +23,6 @@ import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.MonsterCardsApplication;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
@@ -34,6 +33,7 @@ import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.library.LibraryFragmentDirections;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.util.List;
import java.util.UUID;
@@ -174,7 +174,7 @@ public class MonsterImportFragment extends MCFragment {
@Override
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
Logger.logError("Error creating monster", e);
Snackbar.make(mHolder.root, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(mHolder.root, getString(R.string.snackbar_failed_to_create_monster));
}
});
} else {

View File

@@ -19,11 +19,11 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.MonsterCardsApplication;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.SnackbarHelper;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -87,7 +87,7 @@ public class MCFragment extends Fragment {
Logger.logError("Failed to launch document creation picker", e);
View view = getView();
if (view != null) {
Snackbar.make(view, R.string.snackbar_export_failed, Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, R.string.snackbar_export_failed);
}
}
}
@@ -103,14 +103,14 @@ public class MCFragment extends Fragment {
if (view != null) {
String fileName = getFileNameFromUri(context, uri);
String message = getString(R.string.snackbar_export_success, fileName);
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, message);
}
}
} catch (Exception e) {
Logger.logError("Failed to write export content to URI", e);
View view = getView();
if (view != null) {
Snackbar.make(view, R.string.snackbar_export_failed, Snackbar.LENGTH_LONG).show();
SnackbarHelper.showLong(view, R.string.snackbar_export_failed);
}
}
}

View File

@@ -0,0 +1,53 @@
package com.majinnaibu.monstercards.utils;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.google.android.material.snackbar.Snackbar;
/**
* Centralized helper for managing Snackbar notifications and durations across the app.
*/
public class SnackbarHelper {
// Configurable default durations in milliseconds
public static int DEFAULT_SHORT_DURATION = 2500; // 2.5 seconds
public static int DEFAULT_LONG_DURATION = 4000; // 4.0 seconds (e.g. for Undo or action messages)
public static Snackbar makeShort(@NonNull View view, @StringRes int resId) {
return make(view, view.getContext().getString(resId), DEFAULT_SHORT_DURATION);
}
public static Snackbar makeShort(@NonNull View view, @NonNull CharSequence message) {
return make(view, message, DEFAULT_SHORT_DURATION);
}
public static Snackbar makeLong(@NonNull View view, @StringRes int resId) {
return make(view, view.getContext().getString(resId), DEFAULT_LONG_DURATION);
}
public static Snackbar makeLong(@NonNull View view, @NonNull CharSequence message) {
return make(view, message, DEFAULT_LONG_DURATION);
}
public static Snackbar make(@NonNull View view, @NonNull CharSequence message, int durationMs) {
return Snackbar.make(view, message, durationMs);
}
public static void showShort(@NonNull View view, @StringRes int resId) {
makeShort(view, resId).show();
}
public static void showShort(@NonNull View view, @NonNull CharSequence message) {
makeShort(view, message).show();
}
public static void showLong(@NonNull View view, @StringRes int resId) {
makeLong(view, resId).show();
}
public static void showLong(@NonNull View view, @NonNull CharSequence message) {
makeLong(view, message).show();
}
}

View File

@@ -0,0 +1,42 @@
package com.majinnaibu.monstercards.utils;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
/**
* Centralized helper for managing Toast notifications and durations across the app.
*/
public class ToastHelper {
// Configurable default durations
public static int DEFAULT_SHORT_DURATION = Toast.LENGTH_SHORT; // ~2.0 seconds
public static int DEFAULT_LONG_DURATION = Toast.LENGTH_LONG; // ~3.5 seconds
private static Toast sCurrentToast;
public static void showShort(@NonNull Context context, @StringRes int resId) {
show(context, context.getString(resId), DEFAULT_SHORT_DURATION);
}
public static void showShort(@NonNull Context context, @NonNull String message) {
show(context, message, DEFAULT_SHORT_DURATION);
}
public static void showLong(@NonNull Context context, @StringRes int resId) {
show(context, context.getString(resId), DEFAULT_LONG_DURATION);
}
public static void showLong(@NonNull Context context, @NonNull String message) {
show(context, message, DEFAULT_LONG_DURATION);
}
public static void show(@NonNull Context context, @NonNull String message, int duration) {
if (sCurrentToast != null) {
sCurrentToast.cancel();
}
sCurrentToast = Toast.makeText(context.getApplicationContext(), message, duration);
sCurrentToast.show();
}
}