diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3a1de80..57a954b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,6 +12,40 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -25,4 +59,5 @@
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true" />
-
\ No newline at end of file
+
+
diff --git a/app/src/main/java/com/majinnaibu/monstercards/ImportMonsterActivity.java b/app/src/main/java/com/majinnaibu/monstercards/ImportMonsterActivity.java
new file mode 100644
index 0000000..030f8cc
--- /dev/null
+++ b/app/src/main/java/com/majinnaibu/monstercards/ImportMonsterActivity.java
@@ -0,0 +1,318 @@
+package com.majinnaibu.monstercards;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.Spanned;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.google.android.material.snackbar.Snackbar;
+import com.majinnaibu.monstercards.data.MonsterRepository;
+import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
+import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
+import com.majinnaibu.monstercards.helpers.StringHelper;
+import com.majinnaibu.monstercards.models.Monster;
+import com.majinnaibu.monstercards.ui.monster.MonsterDetailViewModel;
+import com.majinnaibu.monstercards.utils.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Objects;
+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 ImportMonsterActivity extends AppCompatActivity {
+
+ private ViewHolder mHolder;
+
+ private MonsterDetailViewModel mViewModel;
+
+ @Override
+ protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fragment_monster);
+ mHolder = new ViewHolder(findViewById(android.R.id.content));
+ mViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
+
+ mViewModel.getName().observe(this, mHolder.name::setText);
+ mViewModel.getMeta().observe(this, mHolder.meta::setText);
+ mViewModel.getArmorClass().observe(this, armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
+ mViewModel.getHitPoints().observe(this, hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
+ mViewModel.getSpeed().observe(this, speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
+ mViewModel.getStrength().observe(this, mHolder.strength::setText);
+ mViewModel.getDexterity().observe(this, mHolder.dexterity::setText);
+ mViewModel.getConstitution().observe(this, mHolder.constitution::setText);
+ mViewModel.getIntelligence().observe(this, mHolder.intelligence::setText);
+ mViewModel.getWisdom().observe(this, mHolder.wisdom::setText);
+ mViewModel.getCharisma().observe(this, mHolder.charisma::setText);
+ mViewModel.getSavingThrows().observe(this, savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
+ mViewModel.getSkills().observe(this, skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
+ mViewModel.getDamageVulnerabilities().observe(this, damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
+ mViewModel.getDamageResistances().observe(this, damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
+ mViewModel.getDamageImmunities().observe(this, damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
+ mViewModel.getConditionImmunities().observe(this, conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
+ mViewModel.getSenses().observe(this, senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
+ mViewModel.getLanguages().observe(this, languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
+ mViewModel.getChallenge().observe(this, challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
+ mViewModel.getAbilities().observe(this, abilities -> setupTraitList(mHolder.abilities, abilities));
+ mViewModel.getActions().observe(this, actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
+ mViewModel.getReactions().observe(this, reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
+ mViewModel.getRegionalEffects().observe(this, regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
+ mViewModel.getLairActions().observe(this, lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
+ mViewModel.getLegendaryActions().observe(this, legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Logger.logDebug("onCreateView");
+ Monster monster = readMonsterFromIntent(getIntent());
+ if (monster != null) {
+ mViewModel.setMonster(monster);
+ }
+ }
+
+ private Monster readMonsterFromIntent(Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+ String type = intent.getType();
+ String json;
+ Uri uri = null;
+ if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
+ uri = extras.getParcelable("android.intent.extra.STREAM");
+ } else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
+ uri = intent.getData();
+ } else {
+ Logger.logError(String.format("unexpected launch configuration action: %s, type: %s, uri: %s", action, type, uri));
+ }
+ if (uri == null) {
+ return null;
+ }
+ json = readContentsOfUri(uri);
+ if (StringHelper.isNullOrEmpty(json)) {
+ return null;
+ }
+ return MonsterImportHelper.fromJSON(json);
+ }
+
+ private String readContentsOfUri(Uri uri) {
+ StringBuilder builder = new StringBuilder();
+ try (InputStream inputStream =
+ getContentResolver().openInputStream(uri);
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(Objects.requireNonNull(inputStream)))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line);
+ }
+ } catch (IOException e) {
+ Logger.logError("error reading file", e);
+ return null;
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ private void setupLabeledTextView(TextView view, String text, int titleId) {
+ String title = getString(titleId);
+ String fullText = String.format("%s %s", title, text);
+ view.setText(Html.fromHtml(fullText));
+ }
+
+ private void setupOptionalTextView(TextView root, String text, int titleId) {
+ String title = getString(titleId);
+ if (StringHelper.isNullOrEmpty(text)) {
+ root.setVisibility(View.GONE);
+ } else {
+ root.setVisibility(View.VISIBLE);
+ }
+ Spanned formatted;
+ if (StringHelper.isNullOrEmpty(title)) {
+ formatted = Html.fromHtml(text);
+ } else {
+ formatted = Html.fromHtml(String.format("%s %s", title, text));
+ }
+ root.setText(formatted);
+ }
+
+ private void setupTraitList(@NonNull LinearLayout root, @NonNull List traits) {
+ setupTraitList(root, traits, null, null);
+ }
+
+ private void setupTraitList(@NonNull LinearLayout root, @NonNull List traits, View label, View divider) {
+ int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
+ DisplayMetrics displayMetrics = null;
+ Resources resources = getResources();
+ if (resources != null) {
+ displayMetrics = resources.getDisplayMetrics();
+ }
+ root.removeAllViews();
+ for (String action : traits) {
+ TextView tvAction = new TextView(this);
+ // TODO: Handle multiline block quotes specially so they stay multiline.
+ // TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
+ tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
+ tvAction.setLayoutParams(layoutParams);
+ root.addView(tvAction);
+ }
+ root.setVisibility(visibility);
+ if (label != null) {
+ label.setVisibility(visibility);
+ }
+ if (divider != null) {
+ divider.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(@NonNull Menu menu) {
+ getMenuInflater().inflate(R.menu.import_monster, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == R.id.menu_action_import_monster) {
+ Logger.logDebug("Menu Item Selected");
+ Monster monster = mViewModel.getMonster();
+ if (monster != null) {
+ monster.id = UUID.randomUUID();
+ MonsterCardsApplication application = (MonsterCardsApplication) getApplication();
+ MonsterRepository repository = application.getMonsterRepository();
+ repository.addMonster(monster).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
+ @Override
+ public void onComplete() {
+ Snackbar.make(
+ mHolder.root,
+ getString(R.string.snackbar_monster_created, monster.name),
+ Snackbar.LENGTH_LONG)
+ .setAction("Action", (_view) -> navigateToEditMonster(monster.id))
+ .show();
+ }
+
+ @Override
+ public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
+ Logger.logError("Error creating monster", e);
+ Snackbar.make(mHolder.root, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
+ }
+ });
+ } else {
+ Logger.logWTF("monsterId cannot be null.");
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void navigateToEditMonster(UUID monsterId) {
+ Logger.logUnimplementedFeature(String.format("navigate to editing the monster %s", monsterId));
+ }
+
+ private static class ViewHolder {
+ final View root;
+ final TextView name;
+ final TextView meta;
+ final TextView armorClass;
+ final TextView hitPoints;
+ final TextView speed;
+ final TextView strength;
+ final TextView dexterity;
+ final TextView constitution;
+ final TextView intelligence;
+ final TextView wisdom;
+ final TextView charisma;
+ final TextView savingThrows;
+ final TextView skills;
+ final TextView damageVulnerabilities;
+ final TextView damageResistances;
+ final TextView damageImmunities;
+ final TextView conditionImmunities;
+ final TextView senses;
+ final TextView languages;
+ final TextView challenge;
+ final LinearLayout abilities;
+ final LinearLayout actions;
+ final TextView actions_label;
+ final ImageView actions_divider;
+ final LinearLayout reactions;
+ final TextView reactions_label;
+ final ImageView reactions_divider;
+ final LinearLayout legendaryActions;
+ final TextView legendaryActions_label;
+ final ImageView legendaryActions_divider;
+ final LinearLayout lairActions;
+ final TextView lairActions_label;
+ final ImageView lairActions_divider;
+ final LinearLayout regionalEffects;
+ final TextView regionalEffects_label;
+ final ImageView regionalEffects_divider;
+
+ ViewHolder(View root) {
+ this.root = root;
+ name = root.findViewById(R.id.name);
+ meta = root.findViewById(R.id.meta);
+ armorClass = root.findViewById(R.id.armorClass);
+ hitPoints = root.findViewById(R.id.hitPoints);
+ speed = root.findViewById(R.id.speed);
+ strength = root.findViewById(R.id.strength);
+ dexterity = root.findViewById(R.id.dexterity);
+ constitution = root.findViewById(R.id.constitution);
+ intelligence = root.findViewById(R.id.intelligence);
+ wisdom = root.findViewById(R.id.wisdom);
+ charisma = root.findViewById(R.id.charisma);
+ savingThrows = root.findViewById(R.id.savingThrows);
+ skills = root.findViewById(R.id.skills);
+ damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
+ damageResistances = root.findViewById(R.id.damageResistances);
+ damageImmunities = root.findViewById(R.id.damageImmunities);
+ conditionImmunities = root.findViewById(R.id.conditionImmunities);
+ senses = root.findViewById(R.id.senses);
+ languages = root.findViewById(R.id.languages);
+ challenge = root.findViewById(R.id.challenge);
+ abilities = root.findViewById(R.id.abilities);
+ actions = root.findViewById(R.id.actions);
+ actions_divider = root.findViewById(R.id.actions_divider);
+ actions_label = root.findViewById(R.id.actions_label);
+ reactions = root.findViewById(R.id.reactions);
+ reactions_divider = root.findViewById(R.id.reactions_divider);
+ reactions_label = root.findViewById(R.id.reactions_label);
+ legendaryActions = root.findViewById(R.id.legendaryActions);
+ legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
+ legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
+ lairActions = root.findViewById(R.id.lairActions);
+ lairActions_divider = root.findViewById(R.id.lairActions_divider);
+ lairActions_label = root.findViewById(R.id.lairActions_label);
+ regionalEffects = root.findViewById(R.id.regionalEffects);
+ regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
+ regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
+ }
+ }
+}
diff --git a/app/src/main/java/com/majinnaibu/monstercards/helpers/MonsterImportHelper.java b/app/src/main/java/com/majinnaibu/monstercards/helpers/MonsterImportHelper.java
new file mode 100644
index 0000000..a25f0dd
--- /dev/null
+++ b/app/src/main/java/com/majinnaibu/monstercards/helpers/MonsterImportHelper.java
@@ -0,0 +1,293 @@
+package com.majinnaibu.monstercards.helpers;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
+import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
+import com.majinnaibu.monstercards.data.enums.AbilityScore;
+import com.majinnaibu.monstercards.data.enums.AdvantageType;
+import com.majinnaibu.monstercards.data.enums.ProficiencyType;
+import com.majinnaibu.monstercards.models.Language;
+import com.majinnaibu.monstercards.models.Monster;
+import com.majinnaibu.monstercards.models.Skill;
+import com.majinnaibu.monstercards.models.Trait;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MonsterImportHelper {
+ public static Monster fromJSON(String json) {
+ JsonParser parser = new JsonParser();
+ JsonObject rootDict = parser.parse(json).getAsJsonObject();
+
+ Monster monster = new Monster();
+ monster.name = Helpers.getString(rootDict, "name");
+ monster.size = Helpers.getString(rootDict, "size");
+ monster.type = Helpers.getString(rootDict, "type");
+ monster.subtype = Helpers.getString(rootDict, "tag");
+ monster.alignment = Helpers.getString(rootDict, "alignment");
+ monster.hitDice = Helpers.getInt(rootDict, "hitDice");
+ monster.armorType = ArmorTypeConverter.armorTypeFromStringValue(Helpers.getString(rootDict, "armorName"));
+ monster.shieldBonus = Helpers.getInt(rootDict, "shieldBonus");
+ monster.naturalArmorBonus = Helpers.getInt(rootDict, "natArmorBonus");
+ monster.otherArmorDescription = Helpers.getString(rootDict, "otherArmorDesc");
+ monster.walkSpeed = Helpers.getInt(rootDict, "speed");
+ monster.burrowSpeed = Helpers.getInt(rootDict, "burrowSpeed");
+ monster.climbSpeed = Helpers.getInt(rootDict, "climbSpeed");
+ monster.flySpeed = Helpers.getInt(rootDict, "flySpeed");
+ monster.canHover = Helpers.getBool(rootDict, "hover");
+ monster.swimSpeed = Helpers.getInt(rootDict, "swimSpeed");
+ monster.hasCustomHP = Helpers.getBool(rootDict, "customHP");
+ monster.hasCustomSpeed = Helpers.getBool(rootDict, "customSpeed");
+ monster.customHPDescription = Helpers.getString(rootDict, "hpText");
+ monster.customSpeedDescription = Helpers.getString(rootDict, "speedDesc");
+ monster.strengthScore = Helpers.getInt(rootDict, "strPoints");
+ monster.dexterityScore = Helpers.getInt(rootDict, "dexPoints");
+ monster.constitutionScore = Helpers.getInt(rootDict, "conPoints");
+ monster.intelligenceScore = Helpers.getInt(rootDict, "intPoints");
+ monster.wisdomScore = Helpers.getInt(rootDict, "wisPoints");
+ monster.charismaScore = Helpers.getInt(rootDict, "chaPoints");
+ Helpers.addSense(monster, rootDict, "blindsight");
+ // Helpers.getBool(rootDict, "blind");
+ Helpers.addSense(monster, rootDict, "darkvision");
+ Helpers.addSense(monster, rootDict, "tremorsense");
+ Helpers.addSense(monster, rootDict, "truesight");
+ monster.telepathyRange = Helpers.getInt(rootDict, "telepathy");
+ monster.challengeRating = ChallengeRatingConverter.challengeRatingFromStringValue(Helpers.getString(rootDict, "cr"));
+ monster.customChallengeRatingDescription = Helpers.getString(rootDict, "customCr");
+ monster.customProficiencyBonus = Helpers.getInt(rootDict, "customProf");
+ // Helpers.getBool(rootDict, "isLegendary");
+ // Helpers.getString(rootDict, "legendariesDescription");
+ // Helpers.getBool(rootDict, "isLair");
+ // Helpers.getString(rootDict, "lairDescription");
+ // Helpers.getString(rootDict, "lairDescriptionEnd");
+ // Helpers.getBool(rootDict, "isRegional");
+ // Helpers.getString(rootDict, "regionalDescription");
+ // Helpers.getString(rootDict, "regionalDescriptionEnd");
+ // properties: []
+ monster.abilities = Helpers.getListOfTraits(rootDict, "abilities");
+ monster.actions = Helpers.getListOfTraits(rootDict, "actions");
+ monster.reactions = Helpers.getListOfTraits(rootDict, "reactions");
+ monster.legendaryActions = Helpers.getListOfTraits(rootDict, "legendaries");
+ monster.lairActions = Helpers.getListOfTraits(rootDict, "lairs");
+ monster.regionalActions = Helpers.getListOfTraits(rootDict, "regionals");
+ Helpers.addSavingThrows(monster, rootDict);
+ // skills: []
+ monster.skills = Helpers.getSetOfSkills(rootDict);
+ // damagetypes: []
+ // specialdamage: []
+ monster.damageImmunities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "i");
+ monster.damageImmunities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "i"));
+ monster.damageResistances = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "r");
+ monster.damageResistances.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "r"));
+ monster.damageVulnerabilities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "v");
+ monster.damageVulnerabilities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "v"));
+ // conditions: []
+ monster.conditionImmunities = Helpers.getSetOfDamageTypes(rootDict, "conditions");
+ // languages: []
+ monster.languages = Helpers.getSetOfLanguages(rootDict, "languages");
+ // understandsBut: ""
+ monster.understandsButDescription = Helpers.getString(rootDict, "understandsBut");
+ // shortName: ""
+ // doubleColumns: true
+ // separationPoint: -1
+ // damage: []
+ // pluralName: ""
+
+ return monster;
+ }
+
+
+ public static class Helpers {
+ public static String getString(JsonObject dict, String name) {
+ return getString(dict, name, "");
+ }
+
+ public static String getString(@NotNull JsonObject dict, String name, String defaultValue) {
+ if (dict.has(name)) {
+ return dict.get(name).getAsString();
+ }
+
+ return defaultValue;
+ }
+
+ public static int getInt(JsonObject dict, String name) {
+ return getInt(dict, name, 0);
+ }
+
+ public static int getInt(@NotNull JsonObject dict, String name, int defaultValue) {
+ if (dict.has(name)) {
+ JsonElement element = dict.get(name);
+ if (element.isJsonPrimitive()) {
+ JsonPrimitive rawValue = element.getAsJsonPrimitive();//dict.getAsJsonPrimitive(name);
+ if (rawValue.isNumber()) {
+ return rawValue.getAsInt();
+ }
+ }
+ }
+ return defaultValue;
+ }
+
+ public static boolean getBool(JsonObject dict, String name) {
+ return getBool(dict, name, false);
+ }
+
+ public static boolean getBool(@NotNull JsonObject dict, String name, boolean defaultValue) {
+ if (dict.has(name)) {
+ JsonElement element = dict.get(name);
+ if (element.isJsonPrimitive()) {
+ JsonPrimitive rawValue = element.getAsJsonPrimitive();
+ if (rawValue.isBoolean()) {
+ return rawValue.getAsBoolean();
+ }
+ }
+ }
+ return defaultValue;
+ }
+
+ @NotNull
+ public static String formatDistance(String name, int distance) {
+ return String.format("%s %d ft.", name, distance);
+ }
+
+ public static void addSense(Monster monster, JsonObject root, String name) {
+ int distance = Helpers.getInt(root, name);
+ if (distance > 0) {
+ monster.senses.add(Helpers.formatDistance(name, distance));
+ }
+ }
+
+ @NotNull
+ public static List getListOfTraits(@NotNull JsonObject dict, String name) {
+ ArrayList traits = new ArrayList<>();
+ if (dict.has(name)) {
+ JsonElement arrayElement = dict.get(name);
+ if (arrayElement.isJsonArray()) {
+ JsonArray array = arrayElement.getAsJsonArray();
+ int size = array.size();
+ for (int index = 0; index < size; index++) {
+ JsonElement jsonElement = array.get(index);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ String traitName = Helpers.getString(jsonObject, "name");
+ String description = Helpers.getString(jsonObject, "description");
+ Trait trait = new Trait(traitName, description);
+ traits.add(trait);
+ }
+ }
+ }
+ }
+ return traits;
+ }
+
+ public static void addSavingThrows(Monster monster, JsonObject root) {
+ if (root.has("sthrows")) {
+ JsonElement arrayElement = root.get("sthrows");
+ if (arrayElement.isJsonArray()) {
+ JsonArray array = arrayElement.getAsJsonArray();
+ int size = array.size();
+ for (int index = 0; index < size; index++) {
+ JsonElement jsonElement = array.get(index);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ String name = Helpers.getString(jsonObject, "name");
+ if ("str".equals(name)) {
+ monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
+ } else if ("dex".equals(name)) {
+ monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
+ } else if ("con".equals(name)) {
+ monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
+ } else if ("int".equals(name)) {
+ monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
+ } else if ("wis".equals(name)) {
+ monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
+ } else if ("cha".equals(name)) {
+ monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static Set getSetOfSkills(JsonObject root) {
+ HashSet skills = new HashSet<>();
+ if (root.has("skills")) {
+ JsonElement arrayElement = root.get("skills");
+ if (arrayElement.isJsonArray()) {
+ JsonArray array = arrayElement.getAsJsonArray();
+ int size = array.size();
+ for (int index = 0; index < size; index++) {
+ JsonElement jsonElement = array.get(index);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ String name = Helpers.getString(jsonObject, "name");
+ String stat = Helpers.getString(jsonObject, "stat");
+ String note = Helpers.getString(jsonObject, "note");
+
+ Skill skill = new Skill(name, AbilityScore.valueOfString(stat), AdvantageType.NONE, " (ex)".equals(note) ? ProficiencyType.EXPERTISE : ProficiencyType.PROFICIENT);
+ skills.add(skill);
+ }
+ }
+ }
+ }
+ return skills;
+ }
+
+ public static Set getSetOfDamageTypes(JsonObject rootDict, String name) {
+ return getSetOfDamageTypes(rootDict, name, null);
+ }
+
+ public static Set getSetOfDamageTypes(JsonObject root, String name, String type) {
+ HashSet damageTypes = new HashSet<>();
+ if (root.has(name)) {
+ JsonElement arrayElement = root.get(name);
+ if (arrayElement.isJsonArray()) {
+ JsonArray array = arrayElement.getAsJsonArray();
+ int size = array.size();
+ for (int index = 0; index < size; index++) {
+ JsonElement jsonElement = array.get(index);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ String dtName = Helpers.getString(jsonObject, "name");
+ String dtType = Helpers.getString(jsonObject, "type");
+ if (type == null || type.equals(dtType)) {
+ damageTypes.add(dtName);
+ }
+ }
+ }
+ }
+ }
+ return damageTypes;
+ }
+
+ public static Set getSetOfLanguages(JsonObject root, String name) {
+ HashSet languages = new HashSet<>();
+ if (root.has(name)) {
+ JsonElement arrayElement = root.get(name);
+ if (arrayElement.isJsonArray()) {
+ JsonArray array = arrayElement.getAsJsonArray();
+ int size = array.size();
+ for (int index = 0; index < size; index++) {
+ JsonElement jsonElement = array.get(index);
+ if (jsonElement.isJsonObject()) {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+ String languageName = Helpers.getString(jsonObject, "name");
+ boolean canSpeak = Helpers.getBool(jsonObject, "speaks");
+ Language language = new Language(languageName, canSpeak);
+ languages.add(language);
+ }
+ }
+ }
+ }
+ return languages;
+ }
+ }
+}
diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailViewModel.java b/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailViewModel.java
index a91a5f9..a0b0bed 100644
--- a/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailViewModel.java
+++ b/app/src/main/java/com/majinnaibu/monstercards/ui/monster/MonsterDetailViewModel.java
@@ -180,6 +180,10 @@ public class MonsterDetailViewModel extends ViewModel {
return mMonsterId;
}
+ public Monster getMonster() {
+ return mMonster;
+ }
+
public void setMonster(Monster monster) {
mMonster = monster;
mAbilities.setValue(mMonster.getAbilityDescriptions());
diff --git a/app/src/main/res/drawable/ic_library_add_24.xml b/app/src/main/res/drawable/ic_library_add_24.xml
new file mode 100644
index 0000000..1dba3d0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_library_add_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/menu/import_monster.xml b/app/src/main/res/menu/import_monster.xml
new file mode 100644
index 0000000..3c0c389
--- /dev/null
+++ b/app/src/main/res/menu/import_monster.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1042774..e0e919e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9,6 +9,7 @@
Add Skill
Add Trait
Edit
+ Import Monster
MonsterCards
CHA
CON