Skip to content

Commit

Permalink
Merge pull request #114 from Carifio24/export-all
Browse files Browse the repository at this point in the history
Add importing/exporting app content
  • Loading branch information
Carifio24 authored Dec 23, 2024
2 parents aad11ca + 7db4bc4 commit 297c78a
Show file tree
Hide file tree
Showing 22 changed files with 881 additions and 77 deletions.
14 changes: 14 additions & 0 deletions app/src/androidTest/java/dnd/jon/spellbook/AndroidTestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dnd.jon.spellbook;

import java.util.Collection;

import static org.junit.Assert.*;

public class AndroidTestUtils {

static <T> void assertCollectionsSameUnordered(Collection<T> collection1, Collection<T> collection2) {
assertEquals(collection1.size(), collection2.size());
assertTrue(collection1.containsAll(collection2));
assertTrue(collection2.containsAll(collection1));
}
}
292 changes: 236 additions & 56 deletions app/src/androidTest/java/dnd/jon/spellbook/InstrumentTest.java

Large diffs are not rendered by default.

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

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

import dnd.jon.spellbook.databinding.ExportAllContentBinding;

public class ExportAllContentDialog extends DialogFragment {

private static final String TAG = "EXPORT_ALL_CONTENT_DIALOG";
private SpellbookViewModel viewModel;

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = requireActivity();
viewModel = new ViewModelProvider(activity, activity.getDefaultViewModelProviderFactory())
.get(SpellbookViewModel.class);

AlertDialog.Builder builder = new AlertDialog.Builder(activity);

final ExportAllContentBinding binding = ExportAllContentBinding.inflate(getLayoutInflater());
builder.setView(binding.getRoot());

final ActivityResultLauncher<String> chooser = registerForActivityResult(new ActivityResultContracts.CreateDocument("application/json"), uri -> {
if (uri == null || uri.getPath() == null) {
Toast.makeText(activity, R.string.error_exporting_app_content, Toast.LENGTH_SHORT).show();
return;
}

try {
final OutputStream outputStream = activity.getContentResolver().openOutputStream(uri);
final JSONObject json = viewModel.allCreatedContent();
final String content = json.toString();
final byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
outputStream.write(bytes);
} catch (IOException | JSONException e) {
final String errorMessage = getString(R.string.error_exporting_app_content);
Log.e(TAG, e.getMessage());
Toast.makeText(activity, errorMessage, Toast.LENGTH_SHORT).show();
}
});

binding.exportContentCancelButton.setOnClickListener((View view) -> this.dismiss());
binding.exportContentFileButton.setOnClickListener((View view) -> chooser.launch(getString(R.string.default_export_content_filename)));
binding.exportContentClipboardButton.setOnClickListener((View view) -> {
String message;
try {
final JSONObject json = viewModel.allCreatedContent();
final String content = json.toString();
final String label = getString(R.string.default_export_content_cliplabel);
AndroidUtils.copyToClipboard(activity, content, label);
message = getString(R.string.app_content_clipboard_success);
} catch (JSONException e) {
message = getString(R.string.error_exporting_app_content);
Log.e(TAG, e.getMessage());
}
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
});

final AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(true);
return dialog;
}
}
110 changes: 110 additions & 0 deletions app/src/main/java/dnd/jon/spellbook/ImportContentDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package dnd.jon.spellbook;

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

import dnd.jon.spellbook.databinding.ImportContentBinding;

public class ImportContentDialog extends DialogFragment {

private static final String TAG = "IMPORT_CONTENT_DIALOG";
private ImportContentBinding binding;
private FragmentActivity activity;
private SpellbookViewModel viewModel;
private ActivityResultLauncher<String[]> importContentFileChooser;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
importContentFileChooser = registerForActivityResult(new ActivityResultContracts.OpenDocument(), uri -> {
final FragmentActivity activity = requireActivity();
boolean complete = false;
int messageID;
if (uri == null || uri.getPath() == null) {
Toast.makeText(activity, getString(R.string.selected_path_null), Toast.LENGTH_SHORT).show();
return;
}

try {
final InputStream inputStream = activity.getContentResolver().openInputStream(uri);
final String text = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining());
final JSONObject json = new JSONObject(text);
final boolean success = viewModel.loadCreatedContent(json);
messageID = success ? R.string.content_loaded_successfully : R.string.issues_loading_some_content;
complete = success;
} catch (FileNotFoundException | JSONException e) {
Log.e(TAG, e.getMessage());
messageID = R.string.json_import_error;
}

Toast.makeText(activity, messageID, Toast.LENGTH_SHORT).show();
if (complete) {
this.dismiss();
}
});
}

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

activity = requireActivity();
viewModel = new ViewModelProvider(activity, activity.getDefaultViewModelProviderFactory())
.get(SpellbookViewModel.class);

AlertDialog.Builder builder = new AlertDialog.Builder(activity);
binding = ImportContentBinding.inflate(getLayoutInflater());
builder.setView(binding.getRoot());

binding.contentImportButton.setOnClickListener(this::importContentFromText);
binding.contentImportFileButton.setOnClickListener(this::importContentFromFile);
binding.contentImportCancelButton.setOnClickListener((v) -> this.dismiss());

return builder.create();
}

private void importContentFromText(View view) {
final String jsonString = binding.contentImportEditText.getText().toString();
int messageID;
boolean complete = true;
try {
final JSONObject json = new JSONObject(jsonString);
final boolean success = viewModel.loadCreatedContent(json);
messageID = success ? R.string.content_loaded_successfully : R.string.issues_loading_some_content;
complete = success;
} catch (JSONException e) {
Log.e(TAG, e.getMessage());
messageID = R.string.json_import_error;
}

Toast.makeText(activity, messageID, Toast.LENGTH_SHORT).show();
if (complete) {
this.dismiss();
}
}

private void importContentFromFile(View view) {
importContentFileChooser.launch(new String[]{"application/json"});
}
}
18 changes: 15 additions & 3 deletions app/src/main/java/dnd/jon/spellbook/ImportSourceDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
import androidx.lifecycle.ViewModelProvider;

import org.javatuples.Pair;
import org.json.JSONException;
import org.json.JSONObject;

import dnd.jon.spellbook.databinding.ImportSourceBinding;


public class ImportSourceDialog extends DialogFragment {
private ImportSourceBinding binding;
private FragmentActivity activity;
Expand All @@ -32,10 +35,19 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {

binding.sourceImportButton.setOnClickListener((v) -> {
final String jsonString = binding.sourceImportEditText.getText().toString();
final Pair<Boolean,String> result = viewModel.addSourceFromText(jsonString);
boolean complete = false;
String message;
try {
final JSONObject json = new JSONObject(jsonString);
final Pair<Boolean, String> result = viewModel.addSourceFromJSON(json);
message = result.getValue1();
complete = result.getValue0();
} catch (JSONException e) {
message = getString(R.string.invalid_json_for, getString(R.string.source));
}

Toast.makeText(activity, result.getValue1(), Toast.LENGTH_SHORT).show();
if (result.getValue0()) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
if (complete) {
this.dismiss();
}
});
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/dnd/jon/spellbook/JSONUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ static Pair<Source, List<Spell>> sourceWithSpellsFromJSON(JSONObject json, Conte
final SpellBuilder builder = new SpellBuilder(context);
for (int i = 0; i < jsonSpells.length(); i++) {
final JSONObject item = jsonSpells.getJSONObject(i);
final Spell spell = codec.parseSpell(item, builder, false);
final Spell spell = codec.parseSpell(item, builder, true);
if (spell != null) {
spells.add(spell);
}
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/dnd/jon/spellbook/LocalizationUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ static String getCurrentLanguage() {
return getLocalizedContext(context, desiredLocale).getResources();
}

static @NonNull Locale getInternalLocale() {
return Locale.US;
}

static @NonNull Context getInternalContext(Context context) {
return getLocalizedContext(context, getInternalLocale());
}

static CasterClass[] supportedClasses() { return CasterClass.values(); }
static Source[] supportedSources() { return Source.values(); }
static Source[] supportedCoreSourcebooks() { return Source.coreSourcebooks(); }
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/dnd/jon/spellbook/SettingsFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

public class SettingsFragment extends PreferenceFragmentCompat {

private static final String EXPORT_CONTENT_DIALOG_TAG = "EXPORT_CONTENT_DIALOG";
private static final String IMPORT_CONTENT_DIALOG_TAG = "IMPORT_CONTENT_DIALOG";

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.settings_screen, rootKey);
Expand Down Expand Up @@ -54,6 +57,24 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
}
}
}

final Preference exportAllPreference = findPreference(getString(R.string.export_all));
if (exportAllPreference != null) {
exportAllPreference.setOnPreferenceClickListener(preference -> {
final ExportAllContentDialog dialog = new ExportAllContentDialog();
dialog.show(requireActivity().getSupportFragmentManager(), EXPORT_CONTENT_DIALOG_TAG);
return false;
});
}

final Preference importContentPreference = findPreference(getString(R.string.import_content));
if (importContentPreference != null) {
importContentPreference.setOnPreferenceClickListener(preference -> {
final ImportContentDialog dialog = new ImportContentDialog();
dialog.show(requireActivity().getSupportFragmentManager(), IMPORT_CONTENT_DIALOG_TAG);
return false;
});
}
}

@Override
Expand Down
22 changes: 16 additions & 6 deletions app/src/main/java/dnd/jon/spellbook/SourceCreationDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import androidx.lifecycle.ViewModelProvider;

import org.javatuples.Pair;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -49,17 +51,25 @@ public void onCreate(Bundle savedInstanceState) {
return;
}

String message;
boolean complete = false;
try {
final InputStream inputStream = activity.getContentResolver().openInputStream(uri);
final String text = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining());
final Pair<Boolean,String> result = viewModel.addSourceFromText(text);
Toast.makeText(activity, result.getValue1(), Toast.LENGTH_SHORT).show();
if (result.getValue0()) {
dismiss();
}
} catch (FileNotFoundException e) {
final JSONObject json = new JSONObject(text);
final Pair<Boolean,String> result = viewModel.addSourceFromJSON(json);
message = result.getValue1();
complete = result.getValue0();

} catch (FileNotFoundException | JSONException e) {
Log.e(TAG, e.getMessage());
message = getString(R.string.invalid_json_for, getString(R.string.source));
}

Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
if (complete) {
dismiss();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
Expand Down Expand Up @@ -70,7 +71,8 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
final OutputStream outputStream = activity.getContentResolver().openOutputStream(uri);
final Source source = viewModel.getCreatedSourceByName(exportName);
final Collection<Spell> spells = viewModel.getCreatedSpellsForSource(source);
final String json = JSONUtils.asJSON(source, activity, spells).toString(4);
final Context context = LocalizationUtils.getInternalContext(getContext());
final String json = JSONUtils.asJSON(source, context, spells).toString(4);
final byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
outputStream.write(bytes);
} catch (IOException | JSONException e) {
Expand Down Expand Up @@ -124,7 +126,8 @@ public void onCopyEvent(String name) {
try {
final Source source = viewModel.getCreatedSourceByName(name);
final Collection<Spell> spells = viewModel.getCreatedSpellsForSource(source);
final String json = JSONUtils.asJSON(source, activity, spells).toString();
final Context context = LocalizationUtils.getInternalContext(getContext());
final String json = JSONUtils.asJSON(source, context, spells).toString();
final String jsonString = json.toString();
final String label = name + " JSON";
AndroidUtils.copyToClipboard(activity, jsonString, label);
Expand Down
Loading

0 comments on commit 297c78a

Please sign in to comment.