Adds monster collections.
This commit is contained in:
@@ -4,6 +4,7 @@ import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.TypeConverters;
|
||||
|
||||
import com.majinnaibu.monstercards.data.CollectionDAO;
|
||||
import com.majinnaibu.monstercards.data.MonsterDAO;
|
||||
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
||||
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||
@@ -12,10 +13,12 @@ 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.UUIDConverter;
|
||||
import com.majinnaibu.monstercards.models.Collection;
|
||||
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.MonsterFTS;
|
||||
|
||||
@Database(entities = {Monster.class, MonsterFTS.class}, version = 3)
|
||||
@Database(entities = {Monster.class, MonsterFTS.class, Collection.class, CollectionMonster.class}, version = 4)
|
||||
@TypeConverters({
|
||||
ArmorTypeConverter.class,
|
||||
ChallengeRatingConverter.class,
|
||||
@@ -27,4 +30,5 @@ import com.majinnaibu.monstercards.models.MonsterFTS;
|
||||
})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract MonsterDAO monsterDAO();
|
||||
public abstract CollectionDAO collectionDAO();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,15 @@ public class MonsterCardsApplication extends Application {
|
||||
database.execSQL("ALTER TABLE new_monsters RENAME TO monsters");
|
||||
}
|
||||
};
|
||||
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `collections` (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `description` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `collection_monsters` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `collection_id` TEXT NOT NULL, `monster_id` TEXT NOT NULL, `ordinal` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`collection_id`) REFERENCES `collections`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`monster_id`) REFERENCES `monsters`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)");
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_collection_monsters_collection_id` ON `collection_monsters` (`collection_id`)");
|
||||
database.execSQL("CREATE INDEX IF NOT EXISTS `index_collection_monsters_monster_id` ON `collection_monsters` (`monster_id`)");
|
||||
}
|
||||
};
|
||||
private MonsterRepository m_monsterLibraryRepository;
|
||||
|
||||
|
||||
@@ -56,6 +65,7 @@ public class MonsterCardsApplication extends Application {
|
||||
AppDatabase m_db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "monsters")
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.addMigrations(MIGRATION_3_4)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
// .fallbackToDestructiveMigration()
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Collection;
|
||||
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||
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 CollectionDAO {
|
||||
|
||||
@Query("SELECT * FROM collections")
|
||||
Flowable<List<Collection>> getAll();
|
||||
|
||||
@Query("SELECT collections.*, COUNT(collection_monsters.id) as monsterCount FROM collections LEFT JOIN collection_monsters ON collections.id = collection_monsters.collection_id GROUP BY collections.id")
|
||||
Flowable<List<CollectionWithCount>> getCollectionsWithCount();
|
||||
|
||||
@Query("SELECT * FROM collections WHERE id = :id LIMIT 1")
|
||||
Flowable<Collection> getById(String id);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
Completable save(Collection... collections);
|
||||
|
||||
@Delete
|
||||
Completable delete(Collection collection);
|
||||
|
||||
@Query("SELECT monsters.* FROM monsters INNER JOIN collection_monsters ON monsters.id = collection_monsters.monster_id WHERE collection_monsters.collection_id = :collectionId ORDER BY collection_monsters.ordinal ASC, collection_monsters.id ASC")
|
||||
Flowable<List<Monster>> getMonstersForCollection(String collectionId);
|
||||
|
||||
@Query("SELECT * FROM collection_monsters WHERE collection_id = :collectionId ORDER BY ordinal ASC, id ASC")
|
||||
Flowable<List<CollectionMonster>> getCollectionMonstersForCollection(String collectionId);
|
||||
|
||||
@Insert
|
||||
Completable addMonsterToCollection(CollectionMonster... collectionMonsters);
|
||||
|
||||
@Query("DELETE FROM collection_monsters WHERE collection_id = :collectionId AND monster_id = :monsterId")
|
||||
Completable removeMonsterFromCollection(String collectionId, String monsterId);
|
||||
|
||||
@Query("DELETE FROM collection_monsters WHERE id = :id")
|
||||
Completable removeCollectionMonsterById(long id);
|
||||
|
||||
@Update
|
||||
Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters);
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.majinnaibu.monstercards.AppDatabase;
|
||||
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.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -80,6 +83,82 @@ public class MonsterRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Flowable<List<Collection>> getCollections() {
|
||||
return m_db.collectionDAO()
|
||||
.getAll()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<List<CollectionWithCount>> getCollectionsWithCount() {
|
||||
return m_db.collectionDAO()
|
||||
.getCollectionsWithCount()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<Collection> getCollection(@NonNull UUID collectionId) {
|
||||
return m_db.collectionDAO()
|
||||
.getById(collectionId.toString())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Completable saveCollection(Collection collection) {
|
||||
Completable result = m_db.collectionDAO().save(collection);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable deleteCollection(Collection collection) {
|
||||
Completable result = m_db.collectionDAO().delete(collection);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Flowable<List<Monster>> getMonstersForCollection(@NonNull UUID collectionId) {
|
||||
return m_db.collectionDAO()
|
||||
.getMonstersForCollection(collectionId.toString())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<List<CollectionMonster>> getCollectionMonstersForCollection(@NonNull UUID collectionId) {
|
||||
return m_db.collectionDAO()
|
||||
.getCollectionMonstersForCollection(collectionId.toString())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Completable addMonsterToCollection(@NonNull UUID collectionId, @NonNull UUID monsterId) {
|
||||
return addMonsterToCollection(collectionId, monsterId, 0);
|
||||
}
|
||||
|
||||
public Completable addMonsterToCollection(@NonNull UUID collectionId, @NonNull UUID monsterId, int ordinal) {
|
||||
CollectionMonster collectionMonster = new CollectionMonster(collectionId, monsterId, ordinal);
|
||||
Completable result = m_db.collectionDAO().addMonsterToCollection(collectionMonster);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable removeMonsterFromCollection(@NonNull UUID collectionId, @NonNull UUID monsterId) {
|
||||
Completable result = m_db.collectionDAO().removeMonsterFromCollection(collectionId.toString(), monsterId.toString());
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable removeCollectionMonsterById(long junctionId) {
|
||||
Completable result = m_db.collectionDAO().removeCollectionMonsterById(junctionId);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters) {
|
||||
Completable result = m_db.collectionDAO().updateCollectionMonsters(collectionMonsters);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Helpers {
|
||||
static boolean monsterMatchesSearch(Monster monster, String searchText) {
|
||||
if (StringHelper.isNullOrEmpty(searchText)) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity(tableName = "collections")
|
||||
public class Collection {
|
||||
|
||||
@PrimaryKey
|
||||
@NonNull
|
||||
public UUID id;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(defaultValue = "")
|
||||
public String name;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(defaultValue = "")
|
||||
public String description;
|
||||
|
||||
public Collection() {
|
||||
this.id = UUID.randomUUID();
|
||||
this.name = "";
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
public Collection(@NonNull String name, @NonNull String description) {
|
||||
this.id = UUID.randomUUID();
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Collection that = (Collection) obj;
|
||||
return Objects.equals(id, that.id) &&
|
||||
Objects.equals(name, that.name) &&
|
||||
Objects.equals(description, that.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, name, description);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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 = "collection_monsters",
|
||||
foreignKeys = {
|
||||
@ForeignKey(
|
||||
entity = Collection.class,
|
||||
parentColumns = "id",
|
||||
childColumns = "collection_id",
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
@ForeignKey(
|
||||
entity = Monster.class,
|
||||
parentColumns = "id",
|
||||
childColumns = "monster_id",
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
},
|
||||
indices = {
|
||||
@Index(value = {"collection_id"}),
|
||||
@Index(value = {"monster_id"})
|
||||
}
|
||||
)
|
||||
public class CollectionMonster {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public long id;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "collection_id")
|
||||
public UUID collectionId;
|
||||
|
||||
@NonNull
|
||||
@ColumnInfo(name = "monster_id")
|
||||
public UUID monsterId;
|
||||
|
||||
@ColumnInfo(name = "ordinal", defaultValue = "0")
|
||||
public int ordinal;
|
||||
|
||||
public CollectionMonster() {
|
||||
this.collectionId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||
this.monsterId = UUID.fromString("00000000-0000-0000-0000-000000000000");
|
||||
this.ordinal = 0;
|
||||
}
|
||||
|
||||
public CollectionMonster(@NonNull UUID collectionId, @NonNull UUID monsterId, int ordinal) {
|
||||
this.collectionId = collectionId;
|
||||
this.monsterId = monsterId;
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import androidx.room.Embedded;
|
||||
|
||||
public class CollectionWithCount {
|
||||
@Embedded
|
||||
public Collection collection;
|
||||
|
||||
public int monsterCount;
|
||||
|
||||
public CollectionWithCount() {
|
||||
this.collection = new Collection();
|
||||
this.monsterCount = 0;
|
||||
}
|
||||
|
||||
public CollectionWithCount(Collection collection, int monsterCount) {
|
||||
this.collection = collection;
|
||||
this.monsterCount = monsterCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
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.Monster;
|
||||
import com.majinnaibu.monstercards.ui.dashboard.DashboardRecyclerViewAdapter;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class CollectionDetailFragment extends MCFragment {
|
||||
|
||||
private CollectionDetailViewModel mViewModel;
|
||||
private UUID mCollectionId;
|
||||
private DashboardRecyclerViewAdapter mAdapter;
|
||||
private final CompositeDisposable mDisposables = new CompositeDisposable();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_collection_detail, container, false);
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
mCollectionId = UUID.fromString(CollectionDetailFragmentArgs.fromBundle(arguments).getCollectionId());
|
||||
|
||||
mViewModel = new ViewModelProvider(this).get(CollectionDetailViewModel.class);
|
||||
|
||||
TextView nameView = root.findViewById(R.id.detail_collection_name);
|
||||
TextView descriptionView = root.findViewById(R.id.detail_collection_description);
|
||||
RecyclerView recyclerView = root.findViewById(R.id.collection_monster_list);
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab_add_monster_to_collection);
|
||||
|
||||
if (fab != null) {
|
||||
fab.setOnClickListener(v -> showAddMonsterDialog());
|
||||
}
|
||||
|
||||
mViewModel.getCollection().observe(getViewLifecycleOwner(), collection -> {
|
||||
if (collection != null) {
|
||||
nameView.setText(collection.name);
|
||||
setTitle(collection.name);
|
||||
if (!TextUtils.isEmpty(collection.description)) {
|
||||
descriptionView.setVisibility(View.VISIBLE);
|
||||
descriptionView.setText(collection.description);
|
||||
} else {
|
||||
descriptionView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mViewModel.getMonsters().observe(getViewLifecycleOwner(), monsters -> {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.submitList(monsters);
|
||||
}
|
||||
});
|
||||
|
||||
setupRecyclerView(recyclerView);
|
||||
loadCollectionData();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void loadCollectionData() {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
mDisposables.add(repository.getCollection(mCollectionId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(collection -> {
|
||||
if (collection != null) {
|
||||
mViewModel.setCollection(collection);
|
||||
}
|
||||
}, Logger::logError));
|
||||
|
||||
mDisposables.add(repository.getMonstersForCollection(mCollectionId)
|
||||
.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);
|
||||
Context context = requireContext();
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
|
||||
if (monster != null) {
|
||||
navigateToMonsterDetail(monster.id);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void showAddMonsterDialog() {
|
||||
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.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
String[] monsterNames = new String[monsters.size()];
|
||||
for (int i = 0; i < monsters.size(); i++) {
|
||||
monsterNames[i] = monsters.get(i).name;
|
||||
}
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.action_add_monster)
|
||||
.setItems(monsterNames, (dialog, which) -> {
|
||||
Monster selectedMonster = monsters.get(which);
|
||||
addMonsterToCollection(selectedMonster);
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
}, Logger::logError));
|
||||
}
|
||||
|
||||
private void addMonsterToCollection(Monster monster) {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
mDisposables.add(repository.addMonsterToCollection(mCollectionId, monster.id)
|
||||
.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_monster_added_to_collection, monster.name, collectionName),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}, Logger::logError));
|
||||
}
|
||||
|
||||
private void navigateToMonsterDetail(@NonNull UUID monsterId) {
|
||||
NavDirections action = CollectionDetailFragmentDirections.actionCollectionDetailFragmentToNavigationMonster(monsterId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mDisposables.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Collection;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CollectionDetailViewModel extends ViewModel {
|
||||
private final MutableLiveData<Collection> mCollection = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<Monster>> mMonsters = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
public CollectionDetailViewModel() {
|
||||
}
|
||||
|
||||
public LiveData<Collection> getCollection() {
|
||||
return mCollection;
|
||||
}
|
||||
|
||||
public void setCollection(Collection collection) {
|
||||
mCollection.setValue(collection);
|
||||
}
|
||||
|
||||
public LiveData<List<Monster>> getMonsters() {
|
||||
return mMonsters;
|
||||
}
|
||||
|
||||
public void setMonsters(List<Monster> monsters) {
|
||||
mMonsters.setValue(monsters);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,158 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
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 java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class CollectionsFragment extends MCFragment {
|
||||
|
||||
private CollectionsViewModel collectionsViewModel;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class);
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||
final TextView textView = root.findViewById(R.id.text_collections);
|
||||
collectionsViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
|
||||
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
|
||||
if (fab != null) {
|
||||
fab.setOnClickListener(v -> showCreateCollectionDialog());
|
||||
}
|
||||
|
||||
RecyclerView recyclerView = root.findViewById(R.id.collection_list);
|
||||
if (recyclerView != null) {
|
||||
setupRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
|
||||
CollectionsRecyclerViewAdapter adapter = new CollectionsRecyclerViewAdapter(
|
||||
context,
|
||||
repository.getCollectionsWithCount(),
|
||||
collection -> navigateToCollectionDetail(collection.id),
|
||||
collection -> repository.deleteCollection(collection)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
recyclerView.setAdapter(adapter);
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> adapter.deleteItem(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void showCreateCollectionDialog() {
|
||||
Context context = requireContext();
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
int padding = (int) (16 * getResources().getDisplayMetrics().density);
|
||||
layout.setPadding(padding, padding, padding, padding);
|
||||
|
||||
final EditText nameInput = new EditText(context);
|
||||
nameInput.setHint(R.string.label_collection_name);
|
||||
layout.addView(nameInput);
|
||||
|
||||
final EditText descInput = new EditText(context);
|
||||
descInput.setHint(R.string.label_collection_description);
|
||||
layout.addView(descInput);
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.title_new_collection)
|
||||
.setView(layout)
|
||||
.setPositiveButton(R.string.dialog_create, (dialog, which) -> {
|
||||
String name = nameInput.getText().toString().trim();
|
||||
String description = descInput.getText().toString().trim();
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = getString(R.string.title_new_collection);
|
||||
}
|
||||
createCollection(name, description);
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void createCollection(@NonNull String name, @NonNull String description) {
|
||||
Collection collection = new Collection(name, description);
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
repository.saveCollection(collection)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.snackbar_collection_created, collection.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("View", v -> navigateToCollectionDetail(collection.id))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void navigateToCollectionDetail(@NonNull UUID collectionId) {
|
||||
NavDirections action = CollectionsFragmentDirections.actionNavigationCollectionsToCollectionDetailFragment(collectionId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Collection;
|
||||
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class CollectionsRecyclerViewAdapter extends RecyclerView.Adapter<CollectionsRecyclerViewAdapter.ViewHolder> {
|
||||
private final Context mContext;
|
||||
private final CollectionCallback mOnClick;
|
||||
private final CollectionCallback mOnDelete;
|
||||
private final Flowable<List<CollectionWithCount>> mItemsObservable;
|
||||
private List<CollectionWithCount> mValues;
|
||||
private Disposable mDisposable;
|
||||
|
||||
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(@NonNull View view) {
|
||||
Collection collection = (Collection) view.getTag();
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onCallback(collection);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public CollectionsRecyclerViewAdapter(Context context,
|
||||
Flowable<List<CollectionWithCount>> itemsObservable,
|
||||
CollectionCallback onClick,
|
||||
CollectionCallback onDelete) {
|
||||
mContext = context;
|
||||
mItemsObservable = itemsObservable;
|
||||
mOnClick = onClick;
|
||||
mOnDelete = onDelete;
|
||||
mValues = new ArrayList<>();
|
||||
mDisposable = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.collection_list_item, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
CollectionWithCount item = mValues.get(position);
|
||||
Collection collection = item.collection;
|
||||
holder.mNameView.setText(collection.name);
|
||||
|
||||
String countText;
|
||||
if (item.monsterCount == 1) {
|
||||
countText = mContext.getString(R.string.format_monster_count_one);
|
||||
} else {
|
||||
countText = mContext.getString(R.string.format_monster_count_other, item.monsterCount);
|
||||
}
|
||||
holder.mCountView.setText(countText);
|
||||
holder.mCountView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!TextUtils.isEmpty(collection.description)) {
|
||||
holder.mDescriptionView.setVisibility(View.VISIBLE);
|
||||
holder.mDescriptionView.setText(collection.description);
|
||||
} else {
|
||||
holder.mDescriptionView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.itemView.setTag(collection);
|
||||
holder.itemView.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
mDisposable = mItemsObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(collections -> {
|
||||
mValues = collections;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onDetachedFromRecyclerView(recyclerView);
|
||||
if (mDisposable != null) {
|
||||
mDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteItem(int position) {
|
||||
if (mOnDelete != null && position >= 0 && position < mValues.size()) {
|
||||
Collection collection = mValues.get(position).collection;
|
||||
mOnDelete.onCallback(collection);
|
||||
}
|
||||
}
|
||||
|
||||
public interface CollectionCallback {
|
||||
void onCallback(Collection collection);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView mNameView;
|
||||
final TextView mDescriptionView;
|
||||
final TextView mCountView;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
mNameView = view.findViewById(R.id.collection_name);
|
||||
mDescriptionView = view.findViewById(R.id.collection_description);
|
||||
mCountView = view.findViewById(R.id.collection_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
|
||||
};
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
protected DashboardRecyclerViewAdapter(ItemCallback onClick) {
|
||||
public DashboardRecyclerViewAdapter(ItemCallback onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@@ -18,14 +18,17 @@ import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
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;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
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;
|
||||
@@ -33,7 +36,10 @@ import com.majinnaibu.monstercards.utils.Logger;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MonsterDetailFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
@@ -172,10 +178,83 @@ public class MonsterDetailFragment extends MCFragment {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_action_add_to_collection) {
|
||||
showAddToCollectionDialog();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void showAddToCollectionDialog() {
|
||||
UUID monsterId = mViewModel.getId().getValue();
|
||||
if (monsterId == null) {
|
||||
return;
|
||||
}
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
repository.getCollections()
|
||||
.firstOrError()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableSingleObserver<List<Collection>>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull List<Collection> collections) {
|
||||
if (collections.isEmpty()) {
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar.make(view, getString(R.string.no_collections_available), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
String[] collectionNames = new String[collections.size()];
|
||||
for (int i = 0; i < collections.size(); i++) {
|
||||
collectionNames[i] = collections.get(i).name;
|
||||
}
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.title_select_collection)
|
||||
.setItems(collectionNames, (dialog, which) -> {
|
||||
Collection selectedCollection = collections.get(which);
|
||||
addMonsterToCollection(selectedCollection, monsterId);
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addMonsterToCollection(@NonNull Collection collection, @NonNull UUID monsterId) {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
repository.addMonsterToCollection(collection.id, monsterId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
|
||||
45
Android/app/src/main/res/layout/collection_list_item.xml
Normal file
45
Android/app/src/main/res/layout/collection_list_item.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="?attr/textAppearanceListItem"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/collection_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textAppearance="?attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.collections.CollectionDetailFragment">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/collection_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_collection_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/detail_collection_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/header_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?android:attr/listDivider"
|
||||
app:layout_constraintTop_toBottomOf="@id/collection_header" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/collection_monster_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:padding="@dimen/padding_normal"
|
||||
app:layout_constraintTop_toBottomOf="@id/header_divider"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:listitem="@layout/card_monster" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_add_monster_to_collection"
|
||||
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_monster"
|
||||
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>
|
||||
@@ -6,17 +6,26 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.collections.CollectionsFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_collections"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/collection_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/collection_list_item" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_add_collection"
|
||||
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_collection"
|
||||
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>
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
<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_to_collection"
|
||||
android:icon="@android:drawable/ic_input_add"
|
||||
android:title="@string/action_add_to_collection"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_action_edit_monster"
|
||||
android:icon="@drawable/ic_edit_24"
|
||||
|
||||
@@ -31,6 +31,21 @@
|
||||
<action
|
||||
android:id="@+id/action_navigation_collections_to_navigation_monster"
|
||||
app:destination="@id/navigation_monster" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_collections_to_collectionDetailFragment"
|
||||
app:destination="@id/collectionDetailFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/collectionDetailFragment"
|
||||
android:name="com.majinnaibu.monstercards.ui.collections.CollectionDetailFragment"
|
||||
android:label="@string/title_collection_details"
|
||||
tools:layout="@layout/fragment_collection_detail">
|
||||
<argument
|
||||
android:name="collection_id"
|
||||
app:argType="string" />
|
||||
<action
|
||||
android:id="@+id/action_collectionDetailFragment_to_navigation_monster"
|
||||
app:destination="@id/navigation_monster" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/navigation_library"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<resources>
|
||||
<string name="action_add_ability">Add Ability</string>
|
||||
<string name="action_add_collection">Add collection</string>
|
||||
<string name="action_add_to_collection">Add to Collection</string>
|
||||
<string name="action_add_condition_immunity">Add Condition</string>
|
||||
<string name="action_add_damage_type">Add Damage Type</string>
|
||||
<string name="action_add_item">Add Item</string>
|
||||
@@ -137,4 +139,17 @@
|
||||
<string name="title_monsterDetails_fmt">%1$s Details</string>
|
||||
<string name="title_search">Search</string>
|
||||
<string name="wisdom_abbreviation">WIS</string>
|
||||
<string name="title_new_collection">New Collection</string>
|
||||
<string name="title_collection_details">Collection Details</string>
|
||||
<string name="title_select_collection">Select Collection</string>
|
||||
<string name="label_collection_name">Collection Name</string>
|
||||
<string name="label_collection_description">Description (optional)</string>
|
||||
<string name="dialog_create">Create</string>
|
||||
<string name="dialog_cancel">Cancel</string>
|
||||
<string name="snackbar_collection_created">Collection %1$s created</string>
|
||||
<string name="snackbar_monster_added_to_collection">Added %1$s to %2$s</string>
|
||||
<string name="snackbar_failed_to_create_collection">Failed to create collection</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user