Cleans up import and export options.

This commit is contained in:
2026-09-20 19:10:43 -07:00
parent 77176a6d4f
commit a3998d6c1d
31 changed files with 2280 additions and 110 deletions

View File

@@ -47,9 +47,18 @@ public interface CollectionDAO {
@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 collection_id = :collectionId")
Completable removeAllMonstersFromCollection(String collectionId);
@Query("DELETE FROM collection_monsters WHERE id = :id")
Completable removeCollectionMonsterById(long id);
@Query("DELETE FROM collection_monsters")
Completable deleteAllCollectionMonsters();
@Query("DELETE FROM collections")
Completable deleteAllCollections();
@Update
Completable updateCollectionMonsters(List<CollectionMonster> collectionMonsters);
}

View File

@@ -36,4 +36,7 @@ public interface MonsterDAO {
@Delete
Completable delete(Monster monster);
@Query("DELETE FROM monsters")
Completable deleteAllMonsters();
}

View File

@@ -9,18 +9,22 @@ 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.DashboardMonsterWithMonster;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.SearchResultItem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
@SuppressWarnings("ResultOfMethodCallIgnored")
@@ -264,6 +268,91 @@ public class MonsterRepository {
return result;
}
public Completable clearAllData() {
return Completable.fromAction(() -> {
m_db.dashboardDAO().clearDashboard().blockingAwait();
m_db.collectionDAO().deleteAllCollectionMonsters().blockingAwait();
m_db.collectionDAO().deleteAllCollections().blockingAwait();
m_db.monsterDAO().deleteAllMonsters().blockingAwait();
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
public Completable removeAllCollections() {
return Completable.fromAction(() -> {
m_db.collectionDAO().deleteAllCollectionMonsters().blockingAwait();
m_db.collectionDAO().deleteAllCollections().blockingAwait();
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
public Completable removeAllMonstersFromCollection(@NonNull UUID collectionId) {
Completable result = m_db.collectionDAO().removeAllMonstersFromCollection(collectionId.toString());
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result;
}
public Single<String> exportCollections() {
return m_db.collectionDAO().getAll().first(new ArrayList<>())
.map(collections -> {
List<BinderExport.CollectionExport> colExports = new ArrayList<>();
for (Collection col : collections) {
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(col.id.toString())
.first(new ArrayList<>()).blockingGet();
colExports.add(new BinderExport.CollectionExport(
col.id.toString(),
col.name,
col.description,
colMonsters
));
}
return new com.majinnaibu.monstercards.exporters.BinderExporter().exportFullBackup(colExports, null);
})
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
public Single<String> exportEverything() {
return Single.zip(
m_db.collectionDAO().getAll().first(new ArrayList<>()),
m_db.monsterDAO().getAll().first(new ArrayList<>()),
m_db.dashboardDAO().getDashboardMonsters().first(new ArrayList<>()),
(collections, allMonsters, dashboardMonsters) -> {
List<BinderExport.CollectionExport> colExports = new ArrayList<>();
Set<UUID> monstersInCollections = new HashSet<>();
for (Collection col : collections) {
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(col.id.toString())
.first(new ArrayList<>()).blockingGet();
for (Monster m : colMonsters) {
monstersInCollections.add(m.id);
}
colExports.add(new BinderExport.CollectionExport(
col.id.toString(),
col.name,
col.description,
colMonsters
));
}
List<Monster> uncategorized = new ArrayList<>();
for (Monster m : allMonsters) {
if (!monstersInCollections.contains(m.id)) {
uncategorized.add(m);
}
}
if (!uncategorized.isEmpty() || colExports.isEmpty()) {
colExports.add(new BinderExport.CollectionExport(
UUID.randomUUID().toString(),
colExports.isEmpty() ? "All Monsters" : "Uncategorized",
"",
colExports.isEmpty() ? allMonsters : uncategorized
));
}
return new com.majinnaibu.monstercards.exporters.BinderExporter().exportFullBackup(colExports, dashboardMonsters);
}
).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
public Completable updateDashboardMonsters(List<DashboardMonster> items) {
Completable result = m_db.dashboardDAO().updateDashboardMonsters(items);
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
@@ -272,65 +361,164 @@ public class MonsterRepository {
public Completable importBinder(@NonNull BinderExport binder) {
return Completable.fromAction(() -> {
if (binder.collections == null || binder.collections.isEmpty()) {
return;
}
// 1. Process Collections & Cards in Binder
if (binder.collections != null && !binder.collections.isEmpty()) {
List<Collection> existingCols = m_db.collectionDAO().getAll().first(new ArrayList<>()).blockingGet();
for (BinderExport.CollectionExport colExport : binder.collections) {
if (colExport.cards == null || colExport.cards.isEmpty()) {
continue;
}
List<Monster> monstersToSave = new ArrayList<>();
for (Monster card : colExport.cards) {
if (card.id == null) {
card.id = UUID.randomUUID();
for (BinderExport.CollectionExport colExport : binder.collections) {
if (colExport == null) {
continue;
}
String colName = colExport.name != null ? colExport.name.trim() : "";
String colDesc = colExport.description != null ? colExport.description.trim() : "";
UUID colExportId = null;
if (colExport.id != null && !colExport.id.trim().isEmpty()) {
try {
colExportId = UUID.fromString(colExport.id.trim());
} catch (Exception ignored) {
}
}
monstersToSave.add(card);
}
m_db.monsterDAO().save(monstersToSave.toArray(new Monster[0])).blockingAwait();
String colName = colExport.name != null ? colExport.name.trim() : "";
if (!colName.isEmpty()) {
List<Collection> existingCols = m_db.collectionDAO().getAll().first(new ArrayList<>()).blockingGet();
Collection targetCollection = null;
for (Collection existing : existingCols) {
if (existing.name != null && existing.name.equalsIgnoreCase(colName)) {
targetCollection = existing;
break;
if (colExportId != null) {
for (Collection existing : existingCols) {
if (existing.id.equals(colExportId)) {
targetCollection = existing;
break;
}
}
}
if (targetCollection == null && !colName.isEmpty()) {
for (Collection existing : existingCols) {
if (existing.name != null && existing.name.equalsIgnoreCase(colName)) {
targetCollection = existing;
break;
}
}
}
UUID targetCollectionId;
if (targetCollection != null) {
targetCollectionId = targetCollection.id;
if (!colDesc.isEmpty() && (targetCollection.description == null || targetCollection.description.isEmpty())) {
targetCollection.description = colDesc;
m_db.collectionDAO().save(targetCollection).blockingAwait();
}
} else {
Collection newCol = new Collection();
newCol.id = UUID.randomUUID();
newCol.name = colName;
newCol.id = colExportId != null ? colExportId : UUID.randomUUID();
newCol.name = !colName.isEmpty() ? colName : "Imported Collection";
newCol.description = colDesc;
m_db.collectionDAO().save(newCol).blockingAwait();
targetCollectionId = newCol.id;
existingCols.add(newCol);
}
List<Monster> colMonsters = m_db.collectionDAO().getMonstersForCollection(targetCollectionId.toString())
.first(new ArrayList<>()).blockingGet();
Set<UUID> existingMonsterIds = new HashSet<>();
for (Monster m : colMonsters) {
existingMonsterIds.add(m.id);
}
if (colExport.cards != null && !colExport.cards.isEmpty()) {
List<Monster> monstersToSave = new ArrayList<>();
Map<UUID, Integer> importedCardCounts = new HashMap<>();
int ordinal = colMonsters.size();
for (Monster monster : monstersToSave) {
if (!existingMonsterIds.contains(monster.id)) {
m_db.collectionDAO().addMonsterToCollection(new CollectionMonster(targetCollectionId, monster.id, ordinal++)).blockingAwait();
existingMonsterIds.add(monster.id);
for (Monster card : colExport.cards) {
if (card != null) {
if (card.id == null) {
card.id = UUID.randomUUID();
}
monstersToSave.add(card);
importedCardCounts.put(card.id, getCount(importedCardCounts, card.id) + 1);
}
}
if (!monstersToSave.isEmpty()) {
m_db.monsterDAO().save(monstersToSave.toArray(new Monster[0])).blockingAwait();
List<Monster> existingColMonsters = m_db.collectionDAO().getMonstersForCollection(targetCollectionId.toString())
.first(new ArrayList<>()).blockingGet();
Map<UUID, Integer> existingCardCounts = new HashMap<>();
for (Monster m : existingColMonsters) {
existingCardCounts.put(m.id, getCount(existingCardCounts, m.id) + 1);
}
int ordinal = existingColMonsters.size();
for (Map.Entry<UUID, Integer> entry : importedCardCounts.entrySet()) {
UUID monsterId = entry.getKey();
int importedCount = entry.getValue();
int existingCount = getCount(existingCardCounts, monsterId);
if (importedCount > existingCount) {
int extraCopiesNeeded = importedCount - existingCount;
for (int k = 0; k < extraCopiesNeeded; k++) {
m_db.collectionDAO().addMonsterToCollection(new CollectionMonster(targetCollectionId, monsterId, ordinal++)).blockingAwait();
}
}
}
}
}
}
}
// 2. Process Dashboard entries in Binder if present
if (binder.dashboard != null && !binder.dashboard.isEmpty()) {
List<Monster> dashboardMonstersToSave = new ArrayList<>();
Map<UUID, Integer> importedDashboardCounts = new HashMap<>();
for (Monster card : binder.dashboard) {
if (card != null) {
if (card.id == null) {
card.id = UUID.randomUUID();
}
dashboardMonstersToSave.add(card);
importedDashboardCounts.put(card.id, getCount(importedDashboardCounts, card.id) + 1);
}
}
if (!dashboardMonstersToSave.isEmpty()) {
m_db.monsterDAO().save(dashboardMonstersToSave.toArray(new Monster[0])).blockingAwait();
List<DashboardMonsterWithMonster> existingDashboard = m_db.dashboardDAO().getDashboardMonstersWithMonster()
.first(new ArrayList<>()).blockingGet();
Map<UUID, Integer> existingDashboardCounts = new HashMap<>();
int currentMaxOrdinal = 0;
for (DashboardMonsterWithMonster entry : existingDashboard) {
if (entry.monster != null) {
existingDashboardCounts.put(entry.monster.id, getCount(existingDashboardCounts, entry.monster.id) + 1);
}
if (entry.dashboardEntry != null && entry.dashboardEntry.ordinal > currentMaxOrdinal) {
currentMaxOrdinal = entry.dashboardEntry.ordinal;
}
}
int ordinal = currentMaxOrdinal + 1;
List<DashboardMonster> extraEntriesToAdd = new ArrayList<>();
for (Map.Entry<UUID, Integer> entry : importedDashboardCounts.entrySet()) {
UUID monsterId = entry.getKey();
int importedCount = entry.getValue();
int existingCount = getCount(existingDashboardCounts, monsterId);
if (importedCount > existingCount) {
int extraNeeded = importedCount - existingCount;
for (int k = 0; k < extraNeeded; k++) {
extraEntriesToAdd.add(new DashboardMonster(monsterId, ordinal++));
}
}
}
if (!extraEntriesToAdd.isEmpty()) {
m_db.dashboardDAO().addMonsterToDashboard(extraEntriesToAdd.toArray(new DashboardMonster[0])).blockingAwait();
}
}
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
private static int getCount(Map<UUID, Integer> map, UUID key) {
if (map == null || key == null) return 0;
Integer val = map.get(key);
return val != null ? val : 0;
}
private static class Helpers {
static boolean monsterMatchesSearch(Monster monster, String searchText) {
if (StringHelper.isNullOrEmpty(searchText)) {

View File

@@ -21,11 +21,23 @@ public enum AbilityScore {
}
public static AbilityScore valueOfString(String string) {
if (string == null) {
return AbilityScore.STRENGTH;
}
String lower = string.trim().toLowerCase(java.util.Locale.ROOT);
for (AbilityScore abilityScore : values()) {
if (abilityScore.stringValue.equals(string)) {
if (abilityScore.stringValue.equalsIgnoreCase(lower)
|| abilityScore.shortDisplayName.equalsIgnoreCase(lower)
|| abilityScore.name().equalsIgnoreCase(lower)) {
return abilityScore;
}
}
if (lower.startsWith("str")) return STRENGTH;
if (lower.startsWith("dex")) return DEXTERITY;
if (lower.startsWith("con")) return CONSTITUTION;
if (lower.startsWith("int")) return INTELLIGENCE;
if (lower.startsWith("wis")) return WISDOM;
if (lower.startsWith("cha")) return CHARISMA;
return AbilityScore.STRENGTH;
}
}