Allow opening card by path.

This commit is contained in:
kenkeiras 2018-01-22 01:03:17 +01:00
parent 53bab05798
commit abd9096c25
10 changed files with 395 additions and 137 deletions

View File

@ -26,25 +26,16 @@ import com.codigoparallevar.minicards.types.Selectable;
import com.codigoparallevar.minicards.types.Tuple2; import com.codigoparallevar.minicards.types.Tuple2;
import com.codigoparallevar.minicards.types.Tuple4; import com.codigoparallevar.minicards.types.Tuple4;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
class CanvasView extends View implements PartGrid { class CanvasView extends View implements PartGrid {
@NonNull @NonNull
ArrayList<Part> parts = new ArrayList<>(); List<Part> parts = new ArrayList<>();
@Nullable @Nullable
Selectable selectedPart; Selectable selectedPart;
@ -74,94 +65,24 @@ class CanvasView extends View implements PartGrid {
public CanvasView(Context context) { public CanvasView(Context context) {
super(context); super(context);
init();
} }
public CanvasView(Context context, AttributeSet attr){ public CanvasView(Context context, AttributeSet attr) {
super(context, attr); super(context, attr);
init();
} }
private void init() { public void loadCard(String path) throws IOException, ErrorLoadingCardException {
this.setBackgroundColor(Color.rgb(4, 69, 99)); CardFile card = CardFile.load(path, this);
if (!loadState()){ name = card.getName();
// Sprinkle some elements around if no state is found setBackgroundColor(card.getBackgroundColor());
parts.add(new Placeholder(this, 50, 50, 750, 500)); parts = card.getParts();
parts.add(new ColorBox(this, 600, 1000, 700, 1100)); List<PartConnection> connections = card.getConnections();
parts.add(new RoundButton(this, 300, 1200, 80, 100));
}
}
private boolean loadState(){
File filesDir = getContext().getFilesDir();
File file = new File(filesDir + "/" + name);
FileReader fileIn = null;
List<PartConnection> connections = new LinkedList<>();
try {
fileIn = new FileReader(file);
char[] data = new char[(int) file.length()];
fileIn.read(data);
JSONObject state = new JSONObject(new String(data));
JSONObject metadata = state.getJSONObject("metadata");
int version = metadata.getInt("version");
if (version != 0) {
return false;
}
// If version 0 -> known structure
JSONArray jsonParts = state.getJSONArray("parts");
for (int i = 0; i < jsonParts.length(); i++){
connections.addAll(deserializeObject(jsonParts.getJSONObject(i)));
}
} catch (IOException e) {
parts.clear();
Log.w("CanvasView", e.getMessage(), e);
return false;
} catch (JSONException e) {
parts.clear();
Log.w("CanvasView", e.getMessage(), e);
return false;
}
resolveConnections(connections); resolveConnections(connections);
try {
fileIn.close();
} catch (IOException e) {
Log.w("PartCanvasView", e.getMessage());
return false;
}
return true;
}
private List<PartConnection> deserializeObject(JSONObject jsonObject) throws JSONException {
String type = jsonObject.getString("_type");
if(type.equals(RoundButton.class.getName())) {
Tuple2<Part, List<PartConnection>> buttonInfo = RoundButton.deserialize(
this,
jsonObject.getJSONObject("_data"));
parts.add(buttonInfo.item1);
return buttonInfo.item2;
}
else if (type.equals(Placeholder.class.getName())) {
parts.add(Placeholder.deserialize(this, jsonObject.getJSONObject("_data")));
return Collections.emptyList();
}
else if (type.equals(ColorBox.class.getName())){
parts.add(ColorBox.deserialize(this, jsonObject.getJSONObject("_data")));
return Collections.emptyList();
}
else {
throw new JSONException("Expected known class, found " + type);
}
} }
// This might not be needed here, and probably could be moved to the CardFile.
private void resolveConnections(List<PartConnection> connections) { private void resolveConnections(List<PartConnection> connections) {
Map<String, Part> partsById = buildPartsById(); Map<String, Part> partsById = buildPartsById();
for (PartConnection connection : connections){ for (PartConnection connection : connections){
@ -349,38 +270,10 @@ class CanvasView extends View implements PartGrid {
} }
private void saveState() throws IOException { private void saveState() throws IOException {
File filesDir = getContext().getFilesDir(); CardFile cardFile = new CardFile(CardFile.getDefaultCardStorage(getContext()));
FileOutputStream fileOut = new FileOutputStream(filesDir + "/" + name); cardFile.setName(name);
cardFile.addParts(parts);
byte[] serialized = serializeState(); cardFile.save(getContext());
fileOut.write(serialized);
fileOut.close();
}
private byte[] serializeState() throws IOException {
final JSONArray partArray = new JSONArray();
for (Part part : parts) {
JSONObject serializedPart = new JSONObject();
try {
serializedPart.put("_type", part.getClass().getName());
serializedPart.put("_data", part.serialize());
} catch (JSONException e) {
throw new IOException(e.getMessage(), e.getCause());
}
partArray.put(serializedPart);
}
final JSONObject metadataObject = new JSONObject(new HashMap<String, Object>(){{
put("version", 0);
}});
final JSONObject state = new JSONObject(new HashMap<String, Object>(){{
put("metadata", metadataObject);
put("parts", partArray);
}});
return state.toString().getBytes("UTF-8");
} }
@Nullable @Nullable

View File

@ -9,9 +9,12 @@ import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import java.io.IOException;
public class CardActivity extends AppCompatActivity { public class CardActivity extends AppCompatActivity {
public final static String INTENT = "com.codigoparallevar.minicards.CARD"; public final static String INTENT = "com.codigoparallevar.minicards.CARD";
public static final String CARD_PATH_KEY = "CARD_PATH";
CanvasView canvasView; CanvasView canvasView;
com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton; com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton;
@ -43,7 +46,22 @@ public class CardActivity extends AppCompatActivity {
// Use manually controlled canvas // Use manually controlled canvas
canvasView = (CanvasView) findViewById(R.id.canvasView); canvasView = (CanvasView) findViewById(R.id.canvasView);
canvasView.setParentActivity(this); canvasView.setParentActivity(this);
String cardPath = this.getIntent().getStringExtra(CARD_PATH_KEY);
if (cardPath == null) {
finish();
}
try {
canvasView.loadCard(cardPath);
} catch (IOException e) {
e.printStackTrace();
finish();
} catch (ErrorLoadingCardException e) {
e.printStackTrace();
finish();
}
// Initialize auxiliary elements
partsHolder = new PartsHolder(this); partsHolder = new PartsHolder(this);
removePartFab = (FloatingActionButton) findViewById(R.id.remove_part_fab); removePartFab = (FloatingActionButton) findViewById(R.id.remove_part_fab);

View File

@ -0,0 +1,264 @@
package com.codigoparallevar.minicards;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.util.Log;
import com.codigoparallevar.minicards.parts.buttons.RoundButton;
import com.codigoparallevar.minicards.parts.samples.ColorBox;
import com.codigoparallevar.minicards.parts.samples.Placeholder;
import com.codigoparallevar.minicards.types.Part;
import com.codigoparallevar.minicards.types.PartConnection;
import com.codigoparallevar.minicards.types.PartGrid;
import com.codigoparallevar.minicards.types.Tuple2;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
public class CardFile {
static final int DEFAULT_BACKGROUND_COLOR = Color.parseColor("#044563");
static final String PATH_SEPARATOR = "/";
@NonNull
private String name;
@NonNull
private List<Part> parts;
private int backgroundColor;
@NonNull
private List<PartConnection> connections;
@NonNull
private String path;
public CardFile(String cardsDirectory) {
this.parts = new ArrayList<>();
this.connections = new ArrayList<>();
this.name = generateAnonymousName();
this.path = cardsDirectory + PATH_SEPARATOR + name + ".json";
this.backgroundColor = DEFAULT_BACKGROUND_COLOR;
}
@NonNull
private String generateAnonymousName() {
return "unnamed-" + UUID.randomUUID().toString().substring(0, 6);
}
@NonNull
public CardFile setName(String name) {
this.name = name;
return this;
}
public void setPath(@NonNull String path) {
this.path = path;
}
@NonNull
public CardFile setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
return this;
}
@NonNull
public String getName() {
return name;
}
int getBackgroundColor() {
return backgroundColor;
}
@NonNull
public List<Part> getParts() {
return parts;
}
@NonNull
public List<PartConnection> getConnections() {
return connections;
}
@NonNull
public String getPath() {
return path;
}
@NonNull
public CardFile addParts(List<Part> parts) {
for (Part part : parts) {
this.addPart(part);
}
return this;
}
@NonNull
private CardFile addPart(Part part) {
this.parts.add(part);
return this;
}
private void addConnections(List<PartConnection> connections) {
for (PartConnection connection : connections) {
addConnection(connection);
}
}
private void addConnection(PartConnection connection) {
connections.add(connection);
}
public static void createDefaultCards(File cardsDir) throws IOException {
// Create a button and a color box, and connect them
RoundButton button = new RoundButton(new StubPartGrid(), 200, 400, 80, 100);
ColorBox box = new ColorBox(new StubPartGrid(), 500, 350, 600, 450);
button.getPressedOutputConnector().connectTo(box.getToggleInputConnector());
CardFile buttonAndColorBoxCard = (new CardFile(cardsDir.getAbsolutePath())
.addPart(button)
.addPart(box)
.setName("Button and color box"));
String buttonAndColorBoxFilePath = (cardsDir.getAbsolutePath()
+ PATH_SEPARATOR + "Button and color box.json");
buttonAndColorBoxCard.save(buttonAndColorBoxFilePath);
}
public void save(Context context) throws IOException {
String cardPath = getDefaultCardStorage(context) + PATH_SEPARATOR + name;
save(cardPath);
}
public void save(String cardPath) throws IOException {
FileOutputStream fileOut = new FileOutputStream(cardPath);
byte[] serialized = serializeState();
fileOut.write(serialized);
fileOut.close();
}
@NonNull
private byte[] serializeState() throws IOException {
final JSONArray partArray = new JSONArray();
for (Part part : parts) {
JSONObject serializedPart = new JSONObject();
try {
serializedPart.put("_type", part.getClass().getName());
serializedPart.put("_data", part.serialize());
} catch (JSONException e) {
throw new IOException(e.getMessage(), e.getCause());
}
partArray.put(serializedPart);
}
final JSONObject metadataObject = new JSONObject(new HashMap<String, Object>(){{
put("version", 0);
put("name", name);
put("backgroundColor", serializeColor(backgroundColor));
}});
final JSONObject state = new JSONObject(new HashMap<String, Object>(){{
put("metadata", metadataObject);
put("parts", partArray);
}});
return state.toString().getBytes("UTF-8");
}
public static String serializeColor(int color) {
return String.format("#%02x%02x%02x",
(color & 0xFF0000) >> 16,
(color & 0x00FF00) >> 8,
(color & 0x0000FF));
}
@NonNull
public static CardFile load(String path, PartGrid grid) throws ErrorLoadingCardException, IOException {
CardFile card = new CardFile(new File(path).getParent());
File file = new File(path);
FileReader fileIn = null;
List<PartConnection> connections = new LinkedList<>();
try {
fileIn = new FileReader(file);
char[] data = new char[(int) file.length()];
fileIn.read(data);
JSONObject state = new JSONObject(new String(data));
JSONObject metadata = state.getJSONObject("metadata");
int version = metadata.getInt("version");
if (version != 0) {
throw new ErrorLoadingCardException(ErrorLoadingCardException.Reason.UNKNOWN_VERSION);
}
card.setPath(path);
card.setName(metadata.getString("name"));
card.setBackgroundColor(Color.parseColor(metadata.getString("backgroundColor")));
// If version 0 -> known structure
JSONArray jsonParts = state.getJSONArray("parts");
for (int i = 0; i < jsonParts.length(); i++) {
Tuple2<Part, List<PartConnection>> info = deserializeObject(grid, jsonParts.getJSONObject(i));
card.addPart(info.item1);
card.addConnections(info.item2);
}
} catch (JSONException e) {
throw new ErrorLoadingCardException(ErrorLoadingCardException.Reason.UNKNOWN_FORMAT);
} finally {
fileIn.close();
}
return card;
}
@NonNull
private static Tuple2<Part, List<PartConnection>> deserializeObject(PartGrid grid,
JSONObject jsonObject) throws JSONException {
String type = jsonObject.getString("_type");
if(type.equals(RoundButton.class.getName())) {
Tuple2<Part, List<PartConnection>> buttonInfo = RoundButton.deserialize(
grid,
jsonObject.getJSONObject("_data"));
return buttonInfo;
}
else if (type.equals(Placeholder.class.getName())) {
return new Tuple2<>(Placeholder.deserialize(grid, jsonObject.getJSONObject("_data")),
Collections.<PartConnection>emptyList());
}
else if (type.equals(ColorBox.class.getName())){
return new Tuple2<>(ColorBox.deserialize(grid, jsonObject.getJSONObject("_data")),
Collections.<PartConnection>emptyList());
}
else {
throw new JSONException("Expected known class, found " + type);
}
}
public static String getDefaultCardStorage(Context context) {
return context.getFilesDir().getAbsolutePath() + CardFile.PATH_SEPARATOR + "cards";
}
}

View File

@ -28,12 +28,13 @@ class CardPreviewArrayAdapter extends ArrayAdapter<PreviewCard> {
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.card_preview, parent, false); View row = inflater.inflate(R.layout.card_preview, parent, false);
PreviewCard card = this.cards[position]; final PreviewCard card = this.cards[position];
row.setOnClickListener(new View.OnClickListener() { row.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent i = new Intent(CardActivity.INTENT); Intent i = new Intent(CardActivity.INTENT);
i.putExtra(CardActivity.CARD_PATH_KEY, card.getPath());
CardPreviewArrayAdapter.this.getContext().startActivity(i); CardPreviewArrayAdapter.this.getContext().startActivity(i);
} }
}); });

View File

@ -1,14 +1,19 @@
package com.codigoparallevar.minicards; package com.codigoparallevar.minicards;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ListView; import android.widget.ListView;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
public class DeckPreviewActivity extends AppCompatActivity { public class DeckPreviewActivity extends AppCompatActivity {
public static final String INTENT = "com.codigoparallevar.minicards.DECK"; public static final String INTENT = "com.codigoparallevar.minicards.DECK";
@ -33,14 +38,48 @@ public class DeckPreviewActivity extends AppCompatActivity {
listView = (ListView) findViewById(R.id.card_deck_list); listView = (ListView) findViewById(R.id.card_deck_list);
cardArrayAdapter = new CardPreviewArrayAdapter(getApplicationContext(), new PreviewCard[]{ cardArrayAdapter = new CardPreviewArrayAdapter(getApplicationContext(), listAvailableCards());
new PreviewCard("Default", 0, PreviewCard.DEFAULT_COLOR),
new PreviewCard("Second", 1, Color.parseColor("#FF00FF")),
new PreviewCard("Greenie", 2, Color.parseColor("#00FF00")),
});
listView.setAdapter(cardArrayAdapter); listView.setAdapter(cardArrayAdapter);
} }
private PreviewCard[] listAvailableCards() {
String cardsPath = CardFile.getDefaultCardStorage(this);
File cardsDir = new File(cardsPath);
if (!cardsDir.exists()) {
cardsDir.mkdir();
}
File[] cardFiles = cardsDir.listFiles();
if (cardFiles.length == 0){
try {
CardFile.createDefaultCards(cardsDir);
} catch (IOException e) {
Log.e("Minicards Deck preview",
"IOException when creating default cards", e);
}
cardFiles = cardsDir.listFiles();
}
List<PreviewCard> cards = new Vector<>(cardFiles.length);
for (File cardFile : cardFiles) {
try {
CardFile card = CardFile.load(cardFile.getAbsolutePath(), new StubPartGrid());
cards.add(new PreviewCard(card.getName(), card.getBackgroundColor(), card.getPath()));
} catch (ErrorLoadingCardException e) {
Log.e("Minicards Deck preview",
"Error loading card, [reason=" + e.getReason()
+ "; path=" + cardFile.getAbsolutePath(),
e);
} catch (IOException e) {
Log.e("Minicards Deck preview",
"IOException loading card, [msg=" + e.getMessage()
+ "; path=" + cardFile.getAbsolutePath(),
e);
}
}
return cards.toArray(new PreviewCard[cards.size()]);
}
} }

View File

@ -0,0 +1,15 @@
package com.codigoparallevar.minicards;
class ErrorLoadingCardException extends Exception {
private final Reason reason;
public ErrorLoadingCardException(Reason reason) {
this.reason = reason;
}
public Reason getReason() {
return reason;
}
public enum Reason {UNKNOWN_FORMAT, UNKNOWN_VERSION}
}

View File

@ -1,28 +1,25 @@
package com.codigoparallevar.minicards; package com.codigoparallevar.minicards;
import android.graphics.Color;
class PreviewCard { class PreviewCard {
public static final int DEFAULT_COLOR = Color.parseColor("#044563");
private final String name; private final String name;
private final int cardId;
private final int color; private final int color;
private String path;
public PreviewCard(String name, int cardId, int color) { public PreviewCard(String name, int color, String path) {
this.name = name; this.name = name;
this.cardId = cardId;
this.color = color; this.color = color;
this.path = path;
} }
public String getName() { public String getName() {
return name; return name;
} }
public int getCardId() {
return cardId;
}
public int getColor() { public int getColor() {
return color; return color;
} }
public String getPath() {
return path;
}
} }

View File

@ -0,0 +1,23 @@
package com.codigoparallevar.minicards;
import com.codigoparallevar.minicards.types.PartGrid;
import com.codigoparallevar.minicards.types.Selectable;
import com.codigoparallevar.minicards.types.Tuple2;
import com.codigoparallevar.minicards.types.connectors.input.SignalInputConnector;
class StubPartGrid implements PartGrid {
@Override
public Selectable getPartOn(int x, int y) {
return null;
}
@Override
public SignalInputConnector getSignalInputConnectorOn(int x, int y) {
return null;
}
@Override
public Tuple2<Integer, Integer> getCenteredOn() {
return null;
}
}

View File

@ -211,6 +211,10 @@ public class RoundButton implements Part {
return null; return null;
} }
public RoundOutputConnector getPressedOutputConnector(){
return _pressedOuputConnector;
}
public static Tuple2<Part, List<PartConnection>> deserialize(PartGrid partGrid, JSONObject data) throws JSONException { public static Tuple2<Part, List<PartConnection>> deserialize(PartGrid partGrid, JSONObject data) throws JSONException {
String id = data.getString("id"); String id = data.getString("id");
int xCenter = data.getInt("x_center"); int xCenter = data.getInt("x_center");

View File

@ -149,6 +149,10 @@ public class ColorBox implements Part {
return Collections.emptyList(); return Collections.emptyList();
} }
public SignalInputConnector getToggleInputConnector() {
return _toggleInputConnector;
}
@Override @Override
public JSONObject serialize() throws JSONException { public JSONObject serialize() throws JSONException {
JSONObject serialized = new JSONObject(); JSONObject serialized = new JSONObject();