Adds card and binder export and import.

This commit is contained in:
2026-09-16 19:25:32 -07:00
parent ccb06960a2
commit b66b653982
19 changed files with 472 additions and 2 deletions

View File

@@ -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<Monster> 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)

View File

@@ -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();

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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<Intent> 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();
}
}