diff --git a/Android/app/src/main/AndroidManifest.xml b/Android/app/src/main/AndroidManifest.xml index 0250817..13c7d91 100644 --- a/Android/app/src/main/AndroidManifest.xml +++ b/Android/app/src/main/AndroidManifest.xml @@ -48,6 +48,8 @@ + + diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java b/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java index 8455dd9..f2e79b1 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java @@ -22,6 +22,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.gson.Gson; import com.majinnaibu.monstercards.helpers.MonsterImportHelper; import com.majinnaibu.monstercards.helpers.StringHelper; +import com.majinnaibu.monstercards.importers.BinderImporter; import com.majinnaibu.monstercards.importers.DnDBeyondImporter; import com.majinnaibu.monstercards.init.AppCenterInitializer; import com.majinnaibu.monstercards.utils.Logger; @@ -110,6 +111,25 @@ 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(); + 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(); + 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(); + }); + return; + } + Toast.makeText(this, R.string.toast_importing_url, Toast.LENGTH_SHORT).show(); Single.fromCallable(() -> MonsterImportHelper.fromJSON(input)) .subscribeOn(Schedulers.io()) @@ -143,7 +163,7 @@ public class MainActivity extends AppCompatActivity { if (uri == null || !isMonsterFile(uri)) { if (uri != null) { - Logger.logError("Ignored file because extension is not supported (.monster, .card): " + uri); + Logger.logError("Ignored file because extension is not supported (.monster, .card, .binder): " + uri); } return null; } @@ -162,7 +182,8 @@ public class MainActivity extends AppCompatActivity { } String lowerName = fileName.toLowerCase(Locale.ROOT); return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt") - || lowerName.endsWith(".card") || lowerName.endsWith(".card.txt"); + || lowerName.endsWith(".card") || lowerName.endsWith(".card.txt") + || lowerName.endsWith(".binder") || lowerName.endsWith(".binder.txt"); } @Nullable diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java b/Android/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java index ab1e81d..ced6e62 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import com.majinnaibu.monstercards.AppDatabase; import com.majinnaibu.monstercards.helpers.StringHelper; +import com.majinnaibu.monstercards.models.BinderExport; import com.majinnaibu.monstercards.models.Collection; import com.majinnaibu.monstercards.models.CollectionMonster; import com.majinnaibu.monstercards.models.CollectionWithCount; @@ -12,7 +13,9 @@ import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.SearchResultItem; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -260,6 +263,67 @@ public class MonsterRepository { return result; } + public Completable importBinder(@NonNull BinderExport binder) { + return Completable.fromAction(() -> { + if (binder.collections == null || binder.collections.isEmpty()) { + return; + } + + for (BinderExport.CollectionExport colExport : binder.collections) { + if (colExport.cards == null || colExport.cards.isEmpty()) { + continue; + } + + List monstersToSave = new ArrayList<>(); + for (Monster card : colExport.cards) { + if (card.id == null) { + card.id = UUID.randomUUID(); + } + 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 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; + } + } + + UUID targetCollectionId; + if (targetCollection != null) { + targetCollectionId = targetCollection.id; + } else { + Collection newCol = new Collection(); + newCol.id = UUID.randomUUID(); + newCol.name = colName; + m_db.collectionDAO().save(newCol).blockingAwait(); + targetCollectionId = newCol.id; + } + + List colMonsters = m_db.collectionDAO().getMonstersForCollection(targetCollectionId.toString()) + .first(new ArrayList<>()).blockingGet(); + Set existingMonsterIds = new HashSet<>(); + for (Monster m : colMonsters) { + existingMonsterIds.add(m.id); + } + + 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); + } + } + } + } + }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); + } + private static class Helpers { static boolean monsterMatchesSearch(Monster monster, String searchText) { if (StringHelper.isNullOrEmpty(searchText)) { diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/BinderExporter.java b/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/BinderExporter.java new file mode 100644 index 0000000..b988333 --- /dev/null +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/BinderExporter.java @@ -0,0 +1,34 @@ +package com.majinnaibu.monstercards.exporters; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.majinnaibu.monstercards.models.BinderExport; +import com.majinnaibu.monstercards.models.Monster; + +import java.util.Collections; +import java.util.List; + +public class BinderExporter { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + @NonNull + public String exportBinder(String collectionName, List monsters) { + BinderExport export = new BinderExport(); + export.schemaVersion = 1; + + if (monsters != null) { + for (Monster monster : monsters) { + monster.schemaVersion = 1; + } + } + + BinderExport.CollectionExport col = new BinderExport.CollectionExport( + collectionName != null ? collectionName : "", + monsters + ); + export.collections = Collections.singletonList(col); + return GSON.toJson(export); + } +} diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/MonsterCardExporter.java b/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/MonsterCardExporter.java new file mode 100644 index 0000000..96bd982 --- /dev/null +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/exporters/MonsterCardExporter.java @@ -0,0 +1,17 @@ +package com.majinnaibu.monstercards.exporters; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.majinnaibu.monstercards.models.Monster; + +public class MonsterCardExporter { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + @NonNull + public String exportCard(@NonNull Monster monster) { + monster.schemaVersion = 1; + return GSON.toJson(monster); + } +} diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/importers/BinderImporter.java b/Android/app/src/main/java/com/majinnaibu/monstercards/importers/BinderImporter.java new file mode 100644 index 0000000..d2f97df --- /dev/null +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/importers/BinderImporter.java @@ -0,0 +1,37 @@ +package com.majinnaibu.monstercards.importers; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.majinnaibu.monstercards.models.BinderExport; + +public class BinderImporter implements EntityImporter { + + private static final Gson GSON = new Gson(); + + @Override + public boolean canImport(@NonNull String input) { + try { + JsonElement el = JsonParser.parseString(input); + if (el.isJsonObject()) { + JsonObject obj = el.getAsJsonObject(); + return obj.has("collections") && obj.get("collections").isJsonArray(); + } + } catch (Exception ignored) { + } + return false; + } + + @NonNull + @Override + public BinderExport parse(@NonNull String input) throws Exception { + BinderExport binder = GSON.fromJson(input, BinderExport.class); + if (binder == null || binder.collections == null) { + throw new IllegalArgumentException("Failed to deserialize Binder JSON"); + } + return binder; + } +} diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/models/BinderExport.java b/Android/app/src/main/java/com/majinnaibu/monstercards/models/BinderExport.java new file mode 100644 index 0000000..f9fcc00 --- /dev/null +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/models/BinderExport.java @@ -0,0 +1,30 @@ +package com.majinnaibu.monstercards.models; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class BinderExport { + @SerializedName("schemaVersion") + public int schemaVersion = 1; + + @SerializedName("collections") + public List collections = new ArrayList<>(); + + public static class CollectionExport { + @SerializedName("name") + public String name; + + @SerializedName("cards") + public List cards = new ArrayList<>(); + + public CollectionExport() { + } + + public CollectionExport(String name, List cards) { + this.name = name != null ? name : ""; + this.cards = cards != null ? cards : new ArrayList<>(); + } + } +} diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java index 2678c34..75c29bf 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/models/Monster.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; +import androidx.room.Ignore; import androidx.room.PrimaryKey; import com.majinnaibu.monstercards.data.enums.AbilityScore; @@ -29,6 +30,9 @@ import java.util.UUID; @SuppressWarnings("unused") public class Monster { + @Ignore + public int schemaVersion = 1; + @PrimaryKey @NonNull public UUID id; diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/collections/CollectionDetailFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/collections/CollectionDetailFragment.java index 911696c..2e1e853 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/collections/CollectionDetailFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/collections/CollectionDetailFragment.java @@ -187,10 +187,28 @@ public class CollectionDetailFragment extends MCFragment { if (item.getItemId() == R.id.menu_action_add_collection_to_dashboard) { addCollectionToDashboard(); return true; + } else if (item.getItemId() == R.id.menu_action_export_collection) { + exportCollection(); + return true; } return super.onOptionsItemSelected(item); } + private void exportCollection() { + Collection collection = mViewModel.getCollection().getValue(); + List monsters = mViewModel.getMonsters().getValue(); + if (collection != null) { + String json = new com.majinnaibu.monstercards.exporters.BinderExporter().exportBinder( + collection.name, + monsters != null ? monsters : new java.util.ArrayList<>() + ); + String collectionName = (collection.name != null && !collection.name.trim().isEmpty()) + ? collection.name.trim() + : "collection"; + exportToFile(collectionName + ".binder", json); + } + } + private void addCollectionToDashboard() { MonsterRepository repository = getMonsterRepository(); mDisposables.add(repository.addCollectionToDashboard(mCollectionId) diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/dashboard/DashboardFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/dashboard/DashboardFragment.java index 67b7810..81be1bb 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/dashboard/DashboardFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/dashboard/DashboardFragment.java @@ -274,10 +274,25 @@ public class DashboardFragment extends MCFragment { } else if (item.getItemId() == R.id.menu_action_clear_dashboard) { clearDashboard(); return true; + } else if (item.getItemId() == R.id.menu_action_export_dashboard) { + exportDashboard(); + return true; } return super.onOptionsItemSelected(item); } + private void exportDashboard() { + mDisposables.add(getMonsterRepository().getDashboardMonsters() + .firstOrError() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(monsters -> { + String json = new com.majinnaibu.monstercards.exporters.BinderExporter().exportBinder("", monsters); + String fileName = getString(R.string.default_filename_dashboard) + ".binder"; + exportToFile(fileName, json); + }, Logger::logError)); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java index f0d1b7f..f3661e9 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/library/LibraryFragment.java @@ -70,10 +70,25 @@ public class LibraryFragment extends MCFragment { if (item.getItemId() == R.id.menu_action_import_from_url) { showImportUrlDialog(); return true; + } else if (item.getItemId() == R.id.menu_action_export_library) { + exportLibrary(); + return true; } return super.onOptionsItemSelected(item); } + private void exportLibrary() { + getMonsterRepository().getMonsters() + .firstOrError() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(monsters -> { + String json = new com.majinnaibu.monstercards.exporters.BinderExporter().exportBinder("", monsters); + String fileName = getString(R.string.default_filename_library) + ".binder"; + exportToFile(fileName, json); + }, Logger::logError); + } + private void showImportUrlDialog() { Context context = requireContext(); AlertDialog.Builder builder = new AlertDialog.Builder(context); diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailFragment.java index 5dc7015..a529740 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailFragment.java @@ -195,10 +195,25 @@ public class MonsterDetailFragment extends MCFragment { } else if (item.getItemId() == R.id.menu_action_share_monster) { shareCurrentMonster(); return true; + } else if (item.getItemId() == R.id.menu_action_export_card) { + exportCurrentMonsterCard(); + return true; } return super.onOptionsItemSelected(item); } + private void exportCurrentMonsterCard() { + Monster monster = mViewModel.getMonster(); + if (monster == null) { + return; + } + String jsonCard = new com.majinnaibu.monstercards.exporters.MonsterCardExporter().exportCard(monster); + String safeName = (monster.name != null && !monster.name.trim().isEmpty()) + ? monster.name.trim() + : "monster"; + exportToFile(safeName + ".card", jsonCard); + } + private void shareCurrentMonster() { Monster monster = mViewModel.getMonster(); if (monster == null) { diff --git a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/shared/MCFragment.java b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/shared/MCFragment.java index 45c3ec7..2ded79c 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/ui/shared/MCFragment.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/ui/shared/MCFragment.java @@ -1,15 +1,53 @@ package com.majinnaibu.monstercards.ui.shared; import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.DocumentsContract; +import android.provider.OpenableColumns; +import android.view.View; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; 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 java.io.OutputStream; +import java.nio.charset.StandardCharsets; public class MCFragment extends Fragment { + private String mPendingExportContent; + private ActivityResultLauncher mCreateDocumentLauncher; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCreateDocumentLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { + Uri uri = result.getData().getData(); + if (uri != null && mPendingExportContent != null) { + writeContentToUri(uri, mPendingExportContent); + } + } + mPendingExportContent = null; + }); + } + public MonsterCardsApplication getApplication() { return (MonsterCardsApplication) requireActivity().getApplication(); } @@ -32,4 +70,76 @@ public class MCFragment extends Fragment { } } } + + protected void exportToFile(@NonNull String defaultFileName, @NonNull String content) { + mPendingExportContent = content; + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_TITLE, defaultFileName); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Uri downloadsUri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3ADownload"); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri); + } + try { + mCreateDocumentLauncher.launch(intent); + } catch (Exception e) { + 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(); + } + } + } + + private void writeContentToUri(@NonNull Uri uri, @NonNull String content) { + Context context = getContext(); + if (context == null) return; + try (OutputStream out = context.getContentResolver().openOutputStream(uri)) { + if (out != null) { + out.write(content.getBytes(StandardCharsets.UTF_8)); + out.flush(); + View view = getView(); + 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(); + } + } + } 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(); + } + } + } + + private String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri) { + String displayName = null; + if ("content".equals(uri.getScheme())) { + try (Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (nameIndex != -1) { + displayName = cursor.getString(nameIndex); + } + } + } catch (Exception e) { + Logger.logError("Error querying display name from content URI", e); + } + } + return displayName != null ? displayName : defaultFileNameFromUri(uri); + } + + private String defaultFileNameFromUri(@NonNull Uri uri) { + String path = uri.getPath(); + if (path != null) { + int lastSlash = path.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < path.length() - 1) { + return path.substring(lastSlash + 1); + } + } + return uri.toString(); + } } diff --git a/Android/app/src/main/res/menu/collection_detail_menu.xml b/Android/app/src/main/res/menu/collection_detail_menu.xml index 464f7b4..02cd759 100644 --- a/Android/app/src/main/res/menu/collection_detail_menu.xml +++ b/Android/app/src/main/res/menu/collection_detail_menu.xml @@ -6,4 +6,9 @@ android:id="@+id/menu_action_add_collection_to_dashboard" android:title="@string/action_add_collection_to_dashboard" app:showAsAction="never" /> + + diff --git a/Android/app/src/main/res/menu/dashboard_menu.xml b/Android/app/src/main/res/menu/dashboard_menu.xml index 81b0839..23180bc 100644 --- a/Android/app/src/main/res/menu/dashboard_menu.xml +++ b/Android/app/src/main/res/menu/dashboard_menu.xml @@ -16,4 +16,9 @@ android:id="@+id/menu_action_clear_dashboard" android:title="@string/action_clear_dashboard" app:showAsAction="never" /> + + diff --git a/Android/app/src/main/res/menu/library_menu.xml b/Android/app/src/main/res/menu/library_menu.xml index 4b0813a..f3b03a5 100644 --- a/Android/app/src/main/res/menu/library_menu.xml +++ b/Android/app/src/main/res/menu/library_menu.xml @@ -6,4 +6,9 @@ android:id="@+id/menu_action_import_from_url" android:title="@string/action_import_from_url" app:showAsAction="never" /> + + diff --git a/Android/app/src/main/res/menu/monster_detail_menu.xml b/Android/app/src/main/res/menu/monster_detail_menu.xml index f084a1e..600a509 100644 --- a/Android/app/src/main/res/menu/monster_detail_menu.xml +++ b/Android/app/src/main/res/menu/monster_detail_menu.xml @@ -23,4 +23,9 @@ android:id="@+id/menu_action_add_to_dashboard" android:title="@string/action_add_to_dashboard" app:showAsAction="never" /> + + diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml index a0ef60c..a4fef13 100644 --- a/Android/app/src/main/res/values/strings.xml +++ b/Android/app/src/main/res/values/strings.xml @@ -171,4 +171,13 @@ Failed to import entity. Please check the URL, ID, or JSON format. Share / Export Monster (.card) Failed to share monster + Export Card + Export Collection + Export Library + Export Dashboard + Exported to %1$s + Failed to export file + Imported binder successfully + MonsterCards Library + MonsterCards Dashboard diff --git a/Android/app/src/test/java/com/majinnaibu/monstercards/exporters/BinderImportExportTest.java b/Android/app/src/test/java/com/majinnaibu/monstercards/exporters/BinderImportExportTest.java new file mode 100644 index 0000000..378f186 --- /dev/null +++ b/Android/app/src/test/java/com/majinnaibu/monstercards/exporters/BinderImportExportTest.java @@ -0,0 +1,59 @@ +package com.majinnaibu.monstercards.exporters; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.majinnaibu.monstercards.importers.BinderImporter; +import com.majinnaibu.monstercards.models.BinderExport; +import com.majinnaibu.monstercards.models.Monster; + +import org.junit.Test; + +import java.util.Collections; +import java.util.UUID; + +public class BinderImportExportTest { + + @Test + public void testExportAndImportBinder() throws Exception { + Monster monster = new Monster(); + monster.id = UUID.randomUUID(); + monster.name = "Goblin Warrior"; + monster.size = "Small"; + monster.type = "humanoid"; + + BinderExporter exporter = new BinderExporter(); + String json = exporter.exportBinder("Goblins", Collections.singletonList(monster)); + + BinderImporter importer = new BinderImporter(); + assertTrue(importer.canImport(json)); + + BinderExport binder = importer.parse(json); + assertEquals(1, binder.schemaVersion); + assertNotNull(binder.collections); + assertEquals(1, binder.collections.size()); + assertEquals("Goblins", binder.collections.get(0).name); + assertEquals(1, binder.collections.get(0).cards.size()); + assertEquals("Goblin Warrior", binder.collections.get(0).cards.get(0).name); + assertEquals(1, binder.collections.get(0).cards.get(0).schemaVersion); + } + + @Test + public void testExportSingleCard() { + Monster monster = new Monster(); + monster.id = UUID.randomUUID(); + monster.name = "Dragon"; + + MonsterCardExporter exporter = new MonsterCardExporter(); + String json = exporter.exportCard(monster); + + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + assertTrue(obj.has("schemaVersion")); + assertEquals(1, obj.get("schemaVersion").getAsInt()); + assertEquals("Dragon", obj.get("name").getAsString()); + } +}