Skip to content

Commit

Permalink
Merge pull request #108 from Carifio24/serialize-to-from-files
Browse files Browse the repository at this point in the history
Allow de/serialization of character profiles and sources from/to files
  • Loading branch information
Carifio24 authored Nov 7, 2024
2 parents d893efa + 6be04bd commit 4a5faae
Show file tree
Hide file tree
Showing 23 changed files with 895 additions and 222 deletions.
11 changes: 9 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ android {
targetSdkVersion 34
versionCode 4001003
versionName "4.1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.release

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// The following argument makes the Android Test Orchestrator run its
// "pm clear" command after each test invocation. This command ensures
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
Expand Down Expand Up @@ -85,13 +90,15 @@ dependencies {
implementation 'io.github.cdimascio:dotenv-java:2.2.4'
implementation 'com.leinardi.android:speed-dial:3.3.0'
//implementation 'org.sufficientlysecure:html-textview:4.0'
implementation files('libs/commons-lang3-3.8.jar')

testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'com.google.truth:truth:1.1.2'
testImplementation 'org.robolectric:robolectric:4.11'
testImplementation 'org.json:json:20180813'

androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation files('libs/commons-lang3-3.8.jar')
androidTestImplementation files('libs/commons-lang3-3.8.jar')
}

This file was deleted.

104 changes: 104 additions & 0 deletions app/src/androidTest/java/dnd/jon/spellbook/InstrumentTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dnd.jon.spellbook;

import android.content.Context;

import androidx.lifecycle.ViewModelProvider;
import androidx.test.core.app.ActivityScenario;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

import java.io.File;
import java.util.List;
import java.util.Map;


/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class InstrumentTest {
@Before
public void setup() {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final File filesDir = context.getFilesDir();
SpellbookUtils.deleteDirectory(filesDir);
}

@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

assertEquals("dnd.jon.spellbook", appContext.getPackageName());
}

@Test
public void testViewModelLoadSource() {
final String json = "{\"name\":\"Test Source\",\"code\":\"TST\",\"spells\":[{\"id\":100000,\"name\":\"Test Spell\",\"desc\":\"abc\",\"higher_level\":\"def\",\"range\":\"3 feet\",\"material\":\"\",\"royalty\":\"\",\"ritual\":false,\"duration\":\"2 seconds\",\"concentration\":true,\"casting_time\":\"1 action\",\"level\":2,\"school\":\"Abjuration\",\"locations\":[{\"sourcebook\":\"TST\",\"page\":-1}],\"components\":[\"V\",\"S\"],\"classes\":[\"Artificer\",\"Paladin\"],\"subclasses\":[],\"tce_expanded_classes\":[],\"ruleset\":\"created\"}]}";
try(ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class)) {
scenario.onActivity(activity -> {
final SpellbookViewModel viewModel = new ViewModelProvider(activity).get(SpellbookViewModel.class);
viewModel.addSourceFromText(json);

final Source[] createdSources = Source.createdSources();
assertEquals(1, createdSources.length);
final Source source = createdSources[0];
assertEquals(source.getCode(), "TST");
assertEquals(source.getDisplayName(), "Test Source");

final List<Spell> createdSpells = viewModel.getCreatedSpells();
assertEquals(1, createdSpells.size());
final Spell spell = createdSpells.get(0);
assertEquals(spell.getName(), "Test Spell");
assertEquals(spell.getDescription(), "abc");
assertEquals(spell.getHigherLevel(), "def");
assertEquals(spell.getID(), 100000);
assertTrue(spell.getMaterial().isEmpty());
assertTrue(spell.getRoyalty().isEmpty());
assertFalse(spell.getRitual());
assertTrue(spell.getConcentration());
assertEquals(spell.getLevel(), 2);
assertEquals(spell.getSchool(), School.ABJURATION);
final Map<Source,Integer> locations = spell.getLocations();
assertEquals(locations.size(), 1);
final Integer page = locations.get(source);
assertNotNull(page);
assertEquals(page.intValue(), -1);

final Range range = spell.getRange();
assertEquals(range.type, Range.RangeType.RANGED);
assertEquals(range.value, 3, 0);
assertEquals(range.unit, LengthUnit.FOOT);

final CastingTime castingTime = spell.getCastingTime();
assertEquals(castingTime.type, CastingTime.CastingTimeType.ACTION);
assertEquals(castingTime.value, 1, 0);
assertEquals(castingTime.unit, TimeUnit.SECOND);

final Duration duration = spell.getDuration();
assertEquals(duration.type, Duration.DurationType.SPANNING);
assertEquals(duration.value, 2, 0);
assertEquals(duration.unit, TimeUnit.SECOND);

assertArrayEquals(spell.getComponents(), new boolean[]{true, true, false, false});
assertArrayEquals(spell.getClasses().toArray(), new CasterClass[]{CasterClass.ARTIFICER, CasterClass.PALADIN});
assertEquals(spell.getSubclasses().size(), 0);
assertEquals(spell.getTashasExpandedClasses().size(), 0);
assertEquals(spell.getRuleset(), Ruleset.RULES_CREATED);




assertEquals(1, viewModel.getCreatedSpellsForSource(source).size());
});
}
}
}
23 changes: 23 additions & 0 deletions app/src/main/java/dnd/jon/spellbook/AndroidUtils.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package dnd.jon.spellbook;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
Expand All @@ -14,6 +17,8 @@
import android.util.DisplayMetrics;
import android.util.Log;

import androidx.annotation.RequiresApi;

import org.javatuples.Pair;

import java.io.File;
Expand All @@ -35,4 +40,22 @@ public static boolean isExternalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(extStorageState);
}

static Intent openDocumentIntent(String fileType, String initialURI) {
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(fileType);

if (initialURI != null && OSUtils.getSDKInt() >= Build.VERSION_CODES.O) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialURI);
}

return intent;
}

static void copyToClipboard(Context context, String text, String label) {
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
final ClipData clipData = ClipData.newPlainText(label, text);
clipboardManager.setPrimaryClip(clipData);
}

}
83 changes: 11 additions & 72 deletions app/src/main/java/dnd/jon/spellbook/CharacterAdapter.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
package dnd.jon.spellbook;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;

import org.json.JSONException;

import dnd.jon.spellbook.databinding.NameRowBinding;

public class CharacterAdapter extends NamedItemAdapter<CharacterAdapter.CharacterRowHolder> {

private static final String confirmDeleteTag = "confirmDeleteCharacter";
private static final String duplicateTag = "duplicateCharacter";
private static final String renameTag = "changeCharacterName";

CharacterAdapter(FragmentActivity fragmentActivity) {
private final NamedItemEventHandler handler;
CharacterAdapter(FragmentActivity fragmentActivity, NamedItemEventHandler handler) {
super(fragmentActivity, SpellbookViewModel::currentCharacterNames);
this.handler = handler;
}

// ViewHolder methods
Expand Down Expand Up @@ -56,79 +46,28 @@ public void bind(String name) {
if (updateItem != null) {
updateItem.setTitle(R.string.rename);
}

popupMenu.setOnMenuItemClickListener((menuItem) -> {
final int itemID = menuItem.getItemId();
final String characterName = binding.getName();
if (itemID == R.id.options_update) {
final Bundle args = new Bundle();
args.putString(NameChangeDialog.nameKey, binding.getName());
final CharacterNameChangeDialog dialog = new CharacterNameChangeDialog();
dialog.setArguments(args);
dialog.show(activity.getSupportFragmentManager(), renameTag);
handler.onUpdateEvent(characterName);
} else if (itemID == R.id.options_duplicate) {
final Bundle args = new Bundle();
args.putParcelable(CreateCharacterDialog.PROFILE_KEY, viewModel.getProfileByName(binding.getName()));
final CreateCharacterDialog dialog = new CreateCharacterDialog();
dialog.setArguments(args);
dialog.show(activity.getSupportFragmentManager(), duplicateTag);
handler.onDuplicateEvent(characterName);
} else if (itemID == R.id.options_delete) {
final Bundle args = new Bundle();
args.putString(DeleteCharacterDialog.NAME_KEY, binding.getName());
final DeleteCharacterDialog dialog = new DeleteCharacterDialog();
dialog.setArguments(args);
dialog.show(activity.getSupportFragmentManager(), confirmDeleteTag);
handler.onDeleteEvent(characterName);
} else if (itemID == R.id.options_export) {
// String permissionNeeded;
// if (GlobalInfo.ANDROID_VERSION >= Build.VERSION_CODES.R) {
// permissionNeeded = Manifest.permission.MANAGE_EXTERNAL_STORAGE;
// } else {
// permissionNeeded = Manifest.permission.WRITE_EXTERNAL_STORAGE;
// }
// final int havePermission = ContextCompat.checkSelfPermission(activity, permissionNeeded);
// if (havePermission == android.content.pm.PackageManager.PERMISSION_GRANTED) {
// //TODO: Implement this saving
// }
try {
final CharacterProfile profile = viewModel.getProfileByName(name);
final String json = profile.toJSON().toString();
final Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, json);
sendIntent.setType("application/json");

final Intent shareIntent = Intent.createChooser(sendIntent, null);
activity.startActivity(shareIntent);
} catch (JSONException e) {
e.printStackTrace();
}
handler.onExportEvent(characterName);
} else if (itemID == R.id.options_copy) {
final CharacterProfile profile = viewModel.getProfileByName(name);
final Context context = v.getContext();
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
String message;
try {
final String json = profile.toJSON().toString();
final ClipData clipData = ClipData.newPlainText(name + " JSON", json);
clipboardManager.setPrimaryClip(clipData);
message = context.getString(R.string.profile_json_copied, name);
} catch (JSONException e) {
e.printStackTrace();
message = context.getString(R.string.error_copying_profile_json, name);
}
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
handler.onCopyEvent(characterName);
}
return false;
});
popupMenu.show();
});

// Set the listener for the label
binding.nameLabel.setOnClickListener((v) -> {
final String charName = binding.getName();
viewModel.setProfileByName(charName);

// Show a Toast message after selection
Toast.makeText(activity, activity.getString(R.string.character_selected_toast, charName), Toast.LENGTH_SHORT).show();
});
binding.nameLabel.setOnClickListener((v) -> handler.onSelectionEvent(binding.getName()));
}
}
}
Expand Down
Loading

0 comments on commit 4a5faae

Please sign in to comment.