diff --git a/Android/app/src/main/AndroidManifest.xml b/Android/app/src/main/AndroidManifest.xml index e35061b..c389764 100644 --- a/Android/app/src/main/AndroidManifest.xml +++ b/Android/app/src/main/AndroidManifest.xml @@ -22,32 +22,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + 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 efac09e..14f2f94 100644 --- a/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java +++ b/Android/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java @@ -1,8 +1,10 @@ package com.majinnaibu.monstercards; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.provider.OpenableColumns; import android.view.MenuItem; import androidx.annotation.NonNull; @@ -23,6 +25,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Locale; import java.util.Objects; public class MainActivity extends AppCompatActivity { @@ -76,26 +79,61 @@ public class MainActivity extends AppCompatActivity { private String readMonsterJSONFromIntent(@NonNull 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))) { + if ("android.intent.action.SEND".equals(action)) { + if (extras != null) { + uri = extras.getParcelable(Intent.EXTRA_STREAM); + } + } else if ("android.intent.action.VIEW".equals(action) || "android.intent.action.EDIT".equals(action)) { uri = intent.getData(); } else { - Logger.logError(String.format("unexpected launch configuration action: %s, type: %s", action, type)); + Logger.logError(String.format("unexpected launch configuration action: %s", action)); } - if (uri == null) { + + if (uri == null || !isMonsterFile(uri)) { + if (uri != null) { + Logger.logError("Ignored file because extension is not .monster or .monster.txt: " + uri); + } return null; } - json = readContentsOfUri(uri); + + String json = readContentsOfUri(uri); if (StringHelper.isNullOrEmpty(json)) { return null; } return json; } + private boolean isMonsterFile(@NonNull Uri uri) { + String fileName = getFileNameFromUri(uri); + if (fileName == null) { + return false; + } + String lowerName = fileName.toLowerCase(Locale.ROOT); + return lowerName.endsWith(".monster") || lowerName.endsWith(".monster.txt"); + } + + @Nullable + private String getFileNameFromUri(@NonNull Uri uri) { + String displayName = null; + if ("content".equals(uri.getScheme())) { + try (Cursor cursor = 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); + } + } + if (displayName == null) { + displayName = uri.getPath(); + } + return displayName; + } + @Nullable private String readContentsOfUri(Uri uri) { StringBuilder builder = new StringBuilder(); diff --git a/docs/Import-Export.md b/docs/Import-Export.md index a3f65b1..b951597 100644 --- a/docs/Import-Export.md +++ b/docs/Import-Export.md @@ -162,3 +162,16 @@ Extend the generic sharing feature with specialized, direct sharing channels (to - **Sub-Step 7.1 (NFC Sharing)**: Share monster data directly between devices via NFC (NDEF records / Android Beam). - **Sub-Step 7.2 (Bluetooth / Wi-Fi Direct)**: Share monster files directly between nearby Android devices via Bluetooth / Wi-Fi. - **Sub-Step 7.3 (Web URL with Embedded Payload)**: Generate a shareable Web URL containing compressed/base64-encoded monster JSON data. + +--- + +## 3. Implementation Checklist & Status + +- [x] **Step 0**: Restrict app intent filters in `AndroidManifest.xml` (`.monster` & `.monster.txt`) and add runtime filename validation in `MainActivity.java`. +- [ ] **Step 1**: Refactor import & conversion code into a shared `EntityImporter` interface and `TetraCubeMonsterImporter` class. +- [ ] **Step 2**: Update Tetra-cube importer class to support the newest Tetra-cube format (`bonusActions`, `mythics`, `blind`, intro descriptions). +- [ ] **Step 3**: Import from D&D Beyond URL (`https://www.dndbeyond.com/characters/49074997` fetching from character service endpoint `character/v2/character/49074997`). +- [ ] **Step 4**: Export to internal format described by Open5e document (`Open5eExporter`). +- [ ] **Step 5**: Import from internal format described by Open5e document (`Open5eImporter`). +- [ ] **Step 6**: Generic Android Share button feature (`ACTION_SEND`). +- [ ] **Step 7**: Specific share targets (7.1 NFC, 7.2 Bluetooth, 7.3 Embedded Web URL).