Makes .monster files open with MonsterCards.
This commit is contained in:
@@ -22,32 +22,40 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for viewing/opening content:// URIs with binary/octet-stream MIME type (.monster) -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:mimeType="application/octet-stream" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for file:// URIs ending in .monster or .monster.txt -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:host="*" />
|
||||||
|
<data android:pathPattern=".*\\.monster" />
|
||||||
|
<data android:pathPattern=".*\\.monster\\.txt" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Intent filter for explicitly sharing files/text to Monster Cards -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
<data android:mimeType="application/octet-stream" />
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
<action android:name="android.intent.action.EDIT" />
|
|
||||||
<action android:name="android.intent.action.PICK" />
|
|
||||||
<action android:name="android.intent.action.INSERT" />
|
|
||||||
<action android:name="android.intent.action.INSERT_OR_EDIT" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.ALTERNATIVE" />
|
|
||||||
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:mimeType="text/plain"
|
|
||||||
android:scheme="content" />
|
|
||||||
<data
|
|
||||||
android:mimeType="application/octet-stream"
|
|
||||||
android:scheme="content" />
|
|
||||||
<data
|
|
||||||
android:mimeType="text/plain"
|
|
||||||
android:scheme="file" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<nav-graph android:value="@navigation/mobile_navigation" />
|
<nav-graph android:value="@navigation/mobile_navigation" />
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.majinnaibu.monstercards;
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -23,6 +25,7 @@ import java.io.BufferedReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
@@ -76,26 +79,61 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
private String readMonsterJSONFromIntent(@NonNull Intent intent) {
|
private String readMonsterJSONFromIntent(@NonNull Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
String type = intent.getType();
|
|
||||||
String json;
|
|
||||||
Uri uri = null;
|
Uri uri = null;
|
||||||
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
|
if ("android.intent.action.SEND".equals(action)) {
|
||||||
uri = extras.getParcelable("android.intent.extra.STREAM");
|
if (extras != null) {
|
||||||
} else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
|
uri = extras.getParcelable(Intent.EXTRA_STREAM);
|
||||||
|
}
|
||||||
|
} else if ("android.intent.action.VIEW".equals(action) || "android.intent.action.EDIT".equals(action)) {
|
||||||
uri = intent.getData();
|
uri = intent.getData();
|
||||||
} else {
|
} 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 || !isMonsterFile(uri)) {
|
||||||
|
if (uri != null) {
|
||||||
|
Logger.logError("Ignored file because extension is not .monster or .monster.txt: " + uri);
|
||||||
}
|
}
|
||||||
if (uri == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
json = readContentsOfUri(uri);
|
|
||||||
|
String json = readContentsOfUri(uri);
|
||||||
if (StringHelper.isNullOrEmpty(json)) {
|
if (StringHelper.isNullOrEmpty(json)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return json;
|
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
|
@Nullable
|
||||||
private String readContentsOfUri(Uri uri) {
|
private String readContentsOfUri(Uri uri) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|||||||
@@ -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.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.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.
|
- **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<T>` 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).
|
||||||
|
|||||||
Reference in New Issue
Block a user