Adds plan to support other import / export formats on android.

This commit is contained in:
2026-09-07 17:03:53 -07:00
parent edc6415ad5
commit 60a26ba81d
2 changed files with 407 additions and 0 deletions

243
docs/ImportFromMonster.md Normal file
View File

@@ -0,0 +1,243 @@
# Import Process & Schema Documentation
> **Platform Note**: This documentation describes the import process and internal database schema for the **Android Application** (`com.majinnaibu.monstercards`).
---
## 1. Overview of the Import Process
The Monster Cards Android application can import monster stat blocks formatted in JSON from `.monster` or `.monster.txt` files shared or opened from other applications (such as file managers, messaging apps, or browsers).
```
+-------------------------------------------------------+
| External .monster / .monster.txt file (JSON Payload) |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| MainActivity (Intent Handling: ACTION_VIEW / SEND) |
| - Extracts raw JSON string from Content/File URI |
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| MonsterImportFragment & MonsterImportViewModel |
| - MonsterImportHelper.fromJSON(json) parses JSON |
| - Maps JSON fields to internal Monster domain model |
| - Renders UI preview with calculated AC, HP, & Markdown|
+-------------------------------------------------------+
|
v
+-------------------------------------------------------+
| User clicks "Import" Menu Action |
| - Generates new UUID |
| - Persists Monster to Room Database (monsters table) |
| - Rebuilds FTS index (monsters_fts) |
+-------------------------------------------------------+
```
---
## 2. Source Format (.monster / .monster.txt) & File Extension Behavior
The `.monster` file format is generated by Tetra-cube's D&D 5e Statblock Generator (`statblock-script.js`).
### File Saving Behavior (`SavedData.SaveToFile`)
```javascript
SaveToFile: () => saveAs(new Blob([JSON.stringify(mon)], {
type: "text/plain;charset=utf-8"
}), mon.name.toLowerCase() + ".monster")
```
- **Desktop Browsers**: Save the file with the specified `.monster` filename.
- **Android Mobile Browsers & DownloadManager**: Because the `Blob` uses MIME type `text/plain;charset=utf-8`, Android file handlers append a `.txt` extension, creating `.monster.txt` files.
---
## 3. JSON Parsing & Field Mapping (`MonsterImportHelper.java`)
`MonsterImportHelper.fromJSON(String json)` uses Gson's `JsonParser` to parse the root JSON object and populate a `Monster` instance.
### Complete Field Mapping Reference
| JSON Field Name | Target `Monster` Field | Type / Default | Description / Conversion Logic |
| :--- | :--- | :--- | :--- |
| `name` | `monster.name` | `String` | Monster name |
| `size` | `monster.size` | `String` | Size string (e.g., `"medium"`, `"large"`) |
| `type` | `monster.type` | `String` | Creature type (e.g., `"humanoid"`, `"dragon"`) |
| `tag` | `monster.subtype` | `String` | Subtype / tag (e.g., `"elf"`, `"shapechanger"`) |
| `alignment` | `monster.alignment` | `String` | Alignment string (e.g., `"neutral good"`) |
| `hitDice` | `monster.hitDice` | `int` (default 1) | Number of hit dice |
| `armorName` | `monster.armorType` | `ArmorType` | Converted via `ArmorTypeConverter.armorTypeFromStringValue()` |
| `shieldBonus` | `monster.shieldBonus` | `int` | Shield AC bonus (+2 if present) |
| `natArmorBonus` | `monster.naturalArmorBonus` | `int` | Additional natural armor AC bonus |
| `otherArmorDesc` | `monster.otherArmorDescription` | `String` | Custom armor description string |
| `speed` | `monster.walkSpeed` | `int` | Walking speed in feet |
| `burrowSpeed` | `monster.burrowSpeed` | `int` | Burrowing speed in feet |
| `climbSpeed` | `monster.climbSpeed` | `int` | Climbing speed in feet |
| `flySpeed` | `monster.flySpeed` | `int` | Flying speed in feet |
| `hover` | `monster.canHover` | `boolean` | Whether flying creature can hover |
| `swimSpeed` | `monster.swimSpeed` | `int` | Swimming speed in feet |
| `customHP` | `monster.hasCustomHP` | `boolean` | Flag for override custom HP string |
| `customSpeed` | `monster.hasCustomSpeed` | `boolean` | Flag for override custom speed string |
| `hpText` | `monster.customHPDescription` | `String` | Custom HP text description |
| `speedDesc` | `monster.customSpeedDescription` | `String` | Custom speed text description |
| `strPoints` | `monster.strengthScore` | `int` (default 10) | Strength ability score |
| `dexPoints` | `monster.dexterityScore` | `int` (default 10) | Dexterity ability score |
| `conPoints` | `monster.constitutionScore` | `int` (default 10) | Constitution ability score |
| `intPoints` | `monster.intelligenceScore` | `int` (default 10) | Intelligence ability score |
| `wisPoints` | `monster.wisdomScore` | `int` (default 10) | Wisdom ability score |
| `chaPoints` | `monster.charismaScore` | `int` (default 10) | Charisma ability score |
| `blindsight` | `monster.senses` | `Set<String>` | Added as `"blindsight <N> ft."` if `N > 0` |
| `darkvision` | `monster.senses` | `Set<String>` | Added as `"darkvision <N> ft."` if `N > 0` |
| `tremorsense` | `monster.senses` | `Set<String>` | Added as `"tremorsense <N> ft."` if `N > 0` |
| `truesight` | `monster.senses` | `Set<String>` | Added as `"truesight <N> ft."` if `N > 0` |
| `telepathy` | `monster.telepathyRange` | `int` | Telepathy range in feet |
| `cr` | `monster.challengeRating` | `ChallengeRating` | Converted via `ChallengeRatingConverter` |
| `customCr` | `monster.customChallengeRatingDescription` | `String` | Custom CR text |
| `customProf` | `monster.customProficiencyBonus` | `int` | Custom proficiency bonus override |
| `abilities` | `monster.abilities` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `actions` | `monster.actions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `bonusActions` | `monster.actions` *(Future)* | `List<Trait>` | Bonus actions array |
| `reactions` | `monster.reactions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `legendaries` | `monster.legendaryActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `mythics` | `monster.legendaryActions` *(Future)* | `List<Trait>` | Mythic actions array |
| `lairs` | `monster.lairActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `regionals` | `monster.regionalActions` | `List<Trait>` | List of JSON objects `{ "name": "...", "desc": "..." }` |
| `sthrows` | Saving throw proficiencies | `ProficiencyType` | Array of objects `{ "name": "str"\|"dex"\|"con"\|"int"\|"wis"\|"cha" }`. Sets proficiency to `PROFICIENT`. |
| `skills` | `monster.skills` | `Set<Skill>` | Array of `{ "name": "...", "stat": "...", "note": " (ex)" }`. `note == " (ex)"` sets `EXPERTISE`. |
| `damageTypes`, `specialdamage` | Immunities / Resistances / Vulnerabilities | `Set<String>` | Array of `{ "name": "...", "type": "i"\|"r"\|"v" }`. Sorted into immunities (`"i"`), resistances (`"r"`), or vulnerabilities (`"v"`). |
| `conditions` | `monster.conditionImmunities` | `Set<String>` | Array of `{ "name": "..." }` |
| `languages` | `monster.languages` | `Set<Language>` | Array of `{ "name": "...", "speaks": true\|false }` |
| `understandsBut` | `monster.understandsButDescription` | `String` | Language qualifier text |
---
## 4. Preview & Database Persistence
1. **ViewModel Computation (`MonsterImportViewModel.java`)**:
- Computes calculated stat block values:
- **AC**: Calculated based on `armorType`, dexterity modifier, shield bonus, natural armor bonus, or custom description.
- **HP**: Calculated as `floor(hitDice * ((dieSize + 1) / 2 + conModifier))` where `dieSize` is derived from size (e.g., Medium = d8, Large = d10).
- **Dynamic Placeholders**: Replaces placeholders in trait descriptions (e.g., `[STR ATK]`, `[WIS SAVE]`) with computed attack bonuses and spell save DCs based on ability scores and proficiency bonuses.
- **Markdown Rendering**: Trait and action descriptions are parsed through `CommonMarkHelper` to format inline HTML/Markdown for display.
2. **Persistence (`MonsterImportFragment.java`)**:
- When the user taps the **Import** action menu button (`R.id.menu_action_import_monster`):
1. Assigns a new `UUID` to `monster.id`.
2. Calls `MonsterRepository.addMonster(monster)` which executes an `INSERT` into the Room database asynchronously on an IO thread via RxJava3.
3. Shows a `Snackbar` confirming import success and navigates to the library/detail view for the imported monster.
---
## 5. Internal Database Schema (`monsters` Table)
The app uses **Room Database** (Database Name: `monsters`, Version: `5`).
### Table: `monsters`
| Column Name | SQLite Data Type | Default Value | Description / Room TypeConverter |
| :--- | :--- | :--- | :--- |
| `id` | `TEXT` | Primary Key | UUID string (`UUIDConverter`) |
| `name` | `TEXT` | `""` | Monster name |
| `size` | `TEXT` | `""` | Size category (e.g. `"medium"`) |
| `type` | `TEXT` | `""` | Creature type |
| `subtype` | `TEXT` | `""` | Creature tag / subtype |
| `alignment` | `TEXT` | `""` | Alignment |
| `strength_score` | `INTEGER` | `10` | Strength score |
| `strength_saving_throw_advantage` | `TEXT` | `"none"` | Advantage type (`"none"`, `"advantage"`, `"disadvantage"`) |
| `strength_saving_throw_proficiency` | `TEXT` | `"none"` | Proficiency type (`"none"`, `"proficient"`, `"expertise"`) |
| `dexterity_score` | `INTEGER` | `10` | Dexterity score |
| `dexterity_saving_throw_advantage` | `TEXT` | `"none"` | Dexterity saving throw advantage |
| `dexterity_saving_throw_proficiency` | `TEXT` | `"none"` | Dexterity saving throw proficiency |
| `constitution_score` | `INTEGER` | `10` | Constitution score |
| `constitution_saving_throw_advantage` | `TEXT` | `"none"` | Constitution saving throw advantage |
| `constitution_saving_throw_proficiency` | `TEXT` | `"none"` | Constitution saving throw proficiency |
| `intelligence_score` | `INTEGER` | `10` | Intelligence score |
| `intelligence_saving_throw_advantage` | `TEXT` | `"none"` | Intelligence saving throw advantage |
| `intelligence_saving_throw_proficiency` | `TEXT` | `"none"` | Intelligence saving throw proficiency |
| `wisdom_score` | `INTEGER` | `10` | Wisdom score |
| `wisdom_saving_throw_advantage` | `TEXT` | `"none"` | Wisdom saving throw advantage |
| `wisdom_saving_throw_proficiency` | `TEXT` | `"none"` | Wisdom saving throw proficiency |
| `charisma_score` | `INTEGER` | `10` | Charisma score |
| `charisma_saving_throw_advantage` | `TEXT` | `"none"` | Charisma saving throw advantage |
| `charisma_saving_throw_proficiency` | `TEXT` | `"none"` | Charisma saving throw proficiency |
| `armor_type` | `TEXT` | `"none"` | String representation of `ArmorType` (`ArmorTypeConverter`) |
| `shield_bonus` | `INTEGER` | `0` | Shield AC bonus |
| `natural_armor_bonus` | `INTEGER` | `0` | Natural armor bonus |
| `other_armor_description` | `TEXT` | `""` | Custom armor description |
| `hit_dice` | `INTEGER` | `1` | Hit dice count |
| `has_custom_hit_points` | `INTEGER` | `0` | Boolean flag (0 = false, 1 = true) |
| `custom_hit_points_description` | `TEXT` | `""` | Override HP string |
| `walk_speed` | `INTEGER` | `0` | Walk speed in ft |
| `burrow_speed` | `INTEGER` | `0` | Burrow speed in ft |
| `climb_speed` | `INTEGER` | `0` | Climb speed in ft |
| `fly_speed` | `INTEGER` | `0` | Fly speed in ft |
| `can_hover` | `INTEGER` | `0` | Boolean flag |
| `swim_speed` | `INTEGER` | `0` | Swim speed in ft |
| `has_custom_speed` | `INTEGER` | `0` | Boolean flag |
| `custom_speed_description` | `TEXT` | NULL | Override speed string |
| `challenge_rating` | `TEXT` | `"1"` | String representation of `ChallengeRating` (`ChallengeRatingConverter`) |
| `custom_challenge_rating_description` | `TEXT` | `""` | Custom CR text |
| `custom_proficiency_bonus` | `INTEGER` | `0` | Custom proficiency bonus override |
| `telepathy_range` | `INTEGER` | `0` | Telepathy range in ft |
| `understands_but_description` | `TEXT` | `""` | Language qualifier description |
| `senses` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
| `skills` | `TEXT` | `"[]"` | JSON array of Skill objects (`SetOfSkillConverter`) |
| `damage_immunities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
| `damage_resistances` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
| `damage_vulnerabilities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
| `condition_immunities` | `TEXT` | `"[]"` | JSON array of strings (`SetOfStringConverter`) |
| `languages` | `TEXT` | `"[]"` | JSON array of Language objects (`SetOfLanguageConverter`) |
| `abilities` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
| `actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
| `reactions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
| `lair_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
| `legendary_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
| `regional_actions` | `TEXT` | `"[]"` | JSON array of Trait objects (`ListOfTraitsConverter`) |
### Virtual FTS Table: `monsters_fts`
Used for full-text search indexing:
```sql
CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts`
USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`);
```
---
## 6. Component Objects Schema (JSON Serialized in DB)
### 1. `Trait`
Used in `abilities`, `actions`, `reactions`, `lair_actions`, `legendary_actions`, and `regional_actions`:
```json
{
"name": "Keen Smell",
"description": "The monster has advantage on Wisdom (Perception) checks that rely on smell."
}
```
### 2. `Skill`
Used in `skills`:
```json
{
"name": "Perception",
"abilityScore": "WISDOM",
"advantageType": "NONE",
"proficiencyType": "PROFICIENT"
}
```
- **`abilityScore`**: `"STRENGTH"`, `"DEXTERITY"`, `"CONSTITUTION"`, `"INTELLIGENCE"`, `"WISDOM"`, `"CHARISMA"`.
- **`advantageType`**: `"NONE"`, `"ADVANTAGE"`, `"DISADVANTAGE"`.
- **`proficiencyType`**: `"NONE"`, `"PROFICIENT"`, `"EXPERTISE"`.
### 3. `Language`
Used in `languages`:
```json
{
"mName": "Common",
"mSpeaks": true
}
```
- **`mName`**: Name of the language (e.g., `"Elvish"`).
- **`mSpeaks`**: `true` if the creature can speak it; `false` if it only understands it.