Makes the dashboard editable.

This commit is contained in:
2026-09-07 15:44:05 -07:00
parent e2a1fafaa9
commit edc6415ad5
15 changed files with 1116 additions and 29 deletions

View File

@@ -5,6 +5,7 @@ import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import com.majinnaibu.monstercards.data.CollectionDAO;
import com.majinnaibu.monstercards.data.DashboardDAO;
import com.majinnaibu.monstercards.data.MonsterDAO;
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
@@ -15,10 +16,11 @@ import com.majinnaibu.monstercards.data.converters.SetOfStringConverter;
import com.majinnaibu.monstercards.data.converters.UUIDConverter;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.CollectionMonster;
import com.majinnaibu.monstercards.models.DashboardMonster;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.MonsterFTS;
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class}, version = 4)
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class, DashboardMonster.class}, version = 5)
@TypeConverters({
ArmorTypeConverter.class,
ChallengeRatingConverter.class,
@@ -31,4 +33,5 @@ import com.majinnaibu.monstercards.models.MonsterFTS;
public abstract class AppDatabase extends RoomDatabase {
public abstract MonsterDAO monsterDAO();
public abstract CollectionDAO collectionDAO();
public abstract DashboardDAO dashboardDAO();
}

View File

@@ -44,6 +44,14 @@ public class MonsterCardsApplication extends Application {
database.execSQL("CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `collection_monsters` (`monster_id`)");
}
};
private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `dashboard_monsters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)");
database.execSQL("CREATE INDEX IF NOT EXISTS `index_dashboard_monsters_monster_id` ON `dashboard_monsters` (`monster_id`)");
database.execSQL("INSERT INTO dashboard_monsters(monster_id, ordinal) SELECT id, 0 FROM monsters");
}
};
private MonsterRepository m_monsterLibraryRepository;
@@ -66,6 +74,7 @@ public class MonsterCardsApplication extends Application {
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4)
.addMigrations(MIGRATION_4_5)
.fallbackToDestructiveMigrationOnDowngrade()
// .fallbackToDestructiveMigration()
.build();

View File

@@ -0,0 +1,39 @@
package com.majinnaibu.monstercards.data;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import com.majinnaibu.monstercards.models.DashboardMonster;
import com.majinnaibu.monstercards.models.Monster;
import java.util.List;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
@Dao
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();
@Query("SELECT * FROM dashboard_monsters ORDER BY ordinal ASC, id ASC")
Flowable<List<DashboardMonster>> getDashboardMonsterEntries();
@Insert
Completable addMonsterToDashboard(DashboardMonster... dashboardMonsters);
@Query("DELETE FROM dashboard_monsters WHERE id = :id")
Completable removeDashboardMonsterById(long id);
@Query("DELETE FROM dashboard_monsters WHERE monster_id = :monsterId")
Completable removeMonsterFromDashboard(String monsterId);
@Query("DELETE FROM dashboard_monsters")
Completable clearDashboard();
@Update
Completable updateDashboardMonsters(List<DashboardMonster> dashboardMonsters);
}

View File

@@ -7,6 +7,7 @@ import com.majinnaibu.monstercards.helpers.StringHelper;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.CollectionMonster;
import com.majinnaibu.monstercards.models.CollectionWithCount;
import com.majinnaibu.monstercards.models.DashboardMonster;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.SearchResultItem;
@@ -197,6 +198,68 @@ public class MonsterRepository {
return result;
}
public Flowable<List<Monster>> getDashboardMonsters() {
return m_db.dashboardDAO()
.getDashboardMonsters()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Flowable<List<DashboardMonster>> getDashboardMonsterEntries() {
return m_db.dashboardDAO()
.getDashboardMonsterEntries()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Completable addMonsterToDashboard(@NonNull UUID monsterId) {
DashboardMonster entry = new DashboardMonster(monsterId, 0);
Completable result = m_db.dashboardDAO().addMonsterToDashboard(entry);
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
public Completable addCollectionToDashboard(@NonNull UUID collectionId) {
return m_db.collectionDAO().getMonstersForCollection(collectionId.toString())
.firstOrError()
.flatMapCompletable(monsters -> {
if (monsters.isEmpty()) {
return Completable.complete();
}
DashboardMonster[] entries = new DashboardMonster[monsters.size()];
for (int i = 0; i < monsters.size(); i++) {
entries[i] = new DashboardMonster(monsters.get(i).id, i);
}
return m_db.dashboardDAO().addMonsterToDashboard(entries);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
public Completable removeMonsterFromDashboard(@NonNull UUID monsterId) {
Completable result = m_db.dashboardDAO().removeMonsterFromDashboard(monsterId.toString());
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
public Completable removeDashboardMonsterById(long id) {
Completable result = m_db.dashboardDAO().removeDashboardMonsterById(id);
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
public Completable clearDashboard() {
Completable result = m_db.dashboardDAO().clearDashboard();
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
public Completable updateDashboardMonsters(List<DashboardMonster> items) {
Completable result = m_db.dashboardDAO().updateDashboardMonsters(items);
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
private static class Helpers {
static boolean monsterMatchesSearch(Monster monster, String searchText) {
if (StringHelper.isNullOrEmpty(searchText)) {

View File

@@ -0,0 +1,43 @@
package com.majinnaibu.monstercards.models;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.util.UUID;
@Entity(
tableName = "dashboard_monsters",
foreignKeys = @ForeignKey(
entity = Monster.class,
parentColumns = "id",
childColumns = "monster_id",
onDelete = ForeignKey.CASCADE
),
indices = @Index(value = {"monster_id"})
)
public class DashboardMonster {
@PrimaryKey(autoGenerate = true)
public long id;
@NonNull
@ColumnInfo(name = "monster_id")
public UUID monsterId;
@ColumnInfo(name = "ordinal", defaultValue = "0")
public int ordinal;
public DashboardMonster() {
this.monsterId = UUID.fromString("00000000-0000-0000-0000-000000000000");
this.ordinal = 0;
}
public DashboardMonster(@NonNull UUID monsterId, int ordinal) {
this.monsterId = monsterId;
this.ordinal = ordinal;
}
}

View File

@@ -4,6 +4,9 @@ import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -44,6 +47,7 @@ public class CollectionDetailFragment extends MCFragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true);
View root = inflater.inflate(R.layout.fragment_collection_detail, container, false);
Bundle arguments = getArguments();
@@ -172,6 +176,36 @@ public class CollectionDetailFragment extends MCFragment {
Navigation.findNavController(requireView()).navigate(action);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.collection_detail_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.menu_action_add_collection_to_dashboard) {
addCollectionToDashboard();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addCollectionToDashboard() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.addCollectionToDashboard(mCollectionId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
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();
}
}, Logger::logError));
}
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -3,52 +3,77 @@ package com.majinnaibu.monstercards.ui.dashboard;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
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 java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class DashboardFragment extends MCFragment {
private DashboardViewModel mViewModel;
private ViewHolder mHolder;
private DashboardRecyclerViewAdapter mAdapter;
private final CompositeDisposable mDisposables = new CompositeDisposable();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true);
mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
mHolder = new ViewHolder(root);
setupRecyclerView(mHolder.list);
FloatingActionButton fab = root.findViewById(R.id.fab_add_to_dashboard);
if (fab != null) {
fab.setOnClickListener(v -> showAddToDashboardOptionsDialog());
}
getMonsterRepository()
.getMonsters()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> mViewModel.setMonsters(monsters));
setupRecyclerView(mHolder.list);
loadDashboardMonsters();
return root;
}
private void loadDashboardMonsters() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getDashboardMonsters()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> mViewModel.setMonsters(monsters), Logger::logError));
}
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 360);
Logger.logWTF(String.format(Locale.US, "Setting column count to %d", columnCount));
Context context = requireContext();
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
@@ -66,6 +91,165 @@ public class DashboardFragment extends MCFragment {
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
}
recyclerView.setAdapter(mAdapter);
ItemTouchHelper.SimpleCallback touchHelperCallback = new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT,
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPos = viewHolder.getAdapterPosition();
int toPos = target.getAdapterPosition();
if (fromPos != RecyclerView.NO_POSITION && toPos != RecyclerView.NO_POSITION) {
List<Monster> currentList = new ArrayList<>(mAdapter.getCurrentList());
if (fromPos < currentList.size() && toPos < currentList.size()) {
Monster moved = currentList.remove(fromPos);
currentList.add(toPos, moved);
mAdapter.submitList(currentList);
}
}
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
List<Monster> currentList = mAdapter.getCurrentList();
if (position < currentList.size()) {
Monster monster = currentList.get(position);
removeMonsterFromDashboard(monster);
}
}
}
};
new ItemTouchHelper(touchHelperCallback).attachToRecyclerView(recyclerView);
}
private void showAddToDashboardOptionsDialog() {
String[] options = new String[]{
getString(R.string.action_add_single_monster),
getString(R.string.action_add_collection_option),
getString(R.string.action_clear_dashboard)
};
new AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_add_to_dashboard)
.setItems(options, (dialog, which) -> {
if (which == 0) {
showAddMonsterPicker();
} else if (which == 1) {
showAddCollectionPicker();
} else if (which == 2) {
clearDashboard();
}
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}
private void showAddMonsterPicker() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getMonsters()
.firstOrError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> {
if (monsters.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.no_monsters_available), Snackbar.LENGTH_LONG).show();
}
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_add_single_monster)
.setItems(names, (dialog, which) -> {
Monster selected = monsters.get(which);
addMonsterToDashboard(selected);
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}, Logger::logError));
}
private void showAddCollectionPicker() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getCollections()
.firstOrError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(collections -> {
if (collections.isEmpty()) {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.no_collections_available), Snackbar.LENGTH_LONG).show();
}
return;
}
String[] names = new String[collections.size()];
for (int i = 0; i < collections.size(); i++) {
names[i] = collections.get(i).name;
}
new AlertDialog.Builder(requireContext())
.setTitle(R.string.action_add_collection_option)
.setItems(names, (dialog, which) -> {
Collection selected = collections.get(which);
addCollectionToDashboard(selected);
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}, Logger::logError));
}
private void addMonsterToDashboard(@NonNull Monster monster) {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.addMonsterToDashboard(monster.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_added_to_dashboard, monster.name), Snackbar.LENGTH_LONG).show();
}
}, Logger::logError));
}
private void addCollectionToDashboard(@NonNull Collection collection) {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.addCollectionToDashboard(collection.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_collection_added_to_dashboard, collection.name), Snackbar.LENGTH_LONG).show();
}
}, Logger::logError));
}
private void removeMonsterFromDashboard(@NonNull Monster monster) {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.removeMonsterFromDashboard(monster.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, Logger::logError));
}
private void clearDashboard() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.clearDashboard()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_dashboard_cleared), Snackbar.LENGTH_LONG).show();
}
}, Logger::logError));
}
private void navigateToMonsterDetail(Monster monster) {
@@ -73,6 +257,33 @@ public class DashboardFragment extends MCFragment {
Navigation.findNavController(requireView()).navigate(action);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.dashboard_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.menu_action_add_single_monster) {
showAddMonsterPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_add_collection_option) {
showAddCollectionPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_clear_dashboard) {
clearDashboard();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mDisposables.clear();
}
private static class ViewHolder {
final RecyclerView list;
@@ -80,5 +291,4 @@ public class DashboardFragment extends MCFragment {
list = root.findViewById(R.id.list);
}
}
}

View File

@@ -7,6 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.DividerItemDecoration;
@@ -19,6 +20,7 @@ import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Monster;
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;
@@ -92,23 +94,16 @@ public class LibraryFragment extends MCFragment {
new DisposableCompletableObserver() {
@Override
public void onComplete() {
View view = getView();
assert view != null;
Snackbar.make(
view,
getString(R.string.snackbar_monster_created, monster.name),
Snackbar.LENGTH_LONG)
.setAction("Action", (_view) -> navigateToMonsterDetail(monster.id))
.show();
navigateToEditMonster(monster.id);
}
@Override
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
Logger.logError("Error creating monster", e);
View view = getView();
assert view != null;
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
if (view != null) {
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
}
}
});
});
@@ -118,4 +113,12 @@ public class LibraryFragment extends MCFragment {
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
Navigation.findNavController(requireView()).navigate(action);
}
protected void navigateToEditMonster(@NonNull UUID monsterId) {
NavController navController = Navigation.findNavController(requireView());
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
navController.navigate(action);
action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
navController.navigate(action);
}
}

View File

@@ -181,10 +181,31 @@ public class MonsterDetailFragment extends MCFragment {
} else if (item.getItemId() == R.id.menu_action_add_to_collection) {
showAddToCollectionDialog();
return true;
} else if (item.getItemId() == R.id.menu_action_add_to_dashboard) {
addCurrentMonsterToDashboard();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addCurrentMonsterToDashboard() {
UUID monsterId = mViewModel.getId().getValue();
if (monsterId == null) {
return;
}
MonsterRepository repository = getMonsterRepository();
repository.addMonsterToDashboard(monsterId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
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();
}
}, Logger::logError);
}
private void showAddToCollectionDialog() {
UUID monsterId = mViewModel.getId().getValue();
if (monsterId == null) {

View File

@@ -20,4 +20,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/card_monster" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_to_dashboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/action_add_to_dashboard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@android:drawable/ic_input_add"
app:tint="@android:color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_action_add_collection_to_dashboard"
android:title="@string/action_add_collection_to_dashboard"
app:showAsAction="never" />
</menu>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_action_add_single_monster"
android:title="@string/action_add_single_monster"
app:showAsAction="never" />
<item
android:id="@+id/menu_action_add_collection_option"
android:title="@string/action_add_collection_option"
app:showAsAction="never" />
<item
android:id="@+id/menu_action_clear_dashboard"
android:title="@string/action_clear_dashboard"
app:showAsAction="never" />
</menu>

View File

@@ -3,14 +3,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_action_add_to_collection"
android:icon="@android:drawable/ic_input_add"
android:title="@string/action_add_to_collection"
app:showAsAction="ifRoom" />
android:id="@+id/menu_action_edit_monster"
android:title="@string/action_edit"
app:showAsAction="never" />
<item
android:id="@+id/menu_action_edit_monster"
android:icon="@drawable/ic_edit_24"
android:title="@string/action_edit"
app:showAsAction="always" />
android:id="@+id/menu_action_add_to_collection"
android:title="@string/action_add_to_collection"
app:showAsAction="never" />
<item
android:id="@+id/menu_action_add_to_dashboard"
android:title="@string/action_add_to_dashboard"
app:showAsAction="never" />
</menu>

View File

@@ -152,4 +152,14 @@
<string name="no_collections_available">No collections available. Create one first!</string>
<string name="format_monster_count_one">1 monster</string>
<string name="format_monster_count_other">%d monsters</string>
<string name="action_add_to_dashboard">Add to Dashboard</string>
<string name="action_add_collection_to_dashboard">Add Collection to Dashboard</string>
<string name="action_add_single_monster">Add Monster</string>
<string name="action_add_collection_option">Add Collection</string>
<string name="action_clear_dashboard">Clear Dashboard</string>
<string name="dialog_add_to_dashboard">Add to Dashboard</string>
<string name="snackbar_added_to_dashboard">Added %1$s to Dashboard</string>
<string name="snackbar_collection_added_to_dashboard">Added collection %1$s to Dashboard</string>
<string name="snackbar_dashboard_cleared">Dashboard cleared</string>
<string name="no_monsters_available">No monsters available in library. Create one first!</string>
</resources>