diff --git a/app/src/main/java/com/codigoparallevar/minicards/CanvasView.java b/app/src/main/java/com/codigoparallevar/minicards/CanvasView.java index 03d78d8..5dc4c9f 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/CanvasView.java +++ b/app/src/main/java/com/codigoparallevar/minicards/CanvasView.java @@ -26,25 +26,16 @@ import com.codigoparallevar.minicards.types.Selectable; import com.codigoparallevar.minicards.types.Tuple2; 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.util.ArrayList; -import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; class CanvasView extends View implements PartGrid { @NonNull - ArrayList parts = new ArrayList<>(); + List parts = new ArrayList<>(); @Nullable Selectable selectedPart; @@ -74,94 +65,24 @@ class CanvasView extends View implements PartGrid { public CanvasView(Context context) { super(context); - init(); } - public CanvasView(Context context, AttributeSet attr){ + public CanvasView(Context context, AttributeSet attr) { super(context, attr); - init(); } - private void init() { - this.setBackgroundColor(Color.rgb(4, 69, 99)); + public void loadCard(String path) throws IOException, ErrorLoadingCardException { + CardFile card = CardFile.load(path, this); - if (!loadState()){ - // Sprinkle some elements around if no state is found - parts.add(new Placeholder(this, 50, 50, 750, 500)); - parts.add(new ColorBox(this, 600, 1000, 700, 1100)); - 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 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; - } + name = card.getName(); + setBackgroundColor(card.getBackgroundColor()); + parts = card.getParts(); + List connections = card.getConnections(); resolveConnections(connections); - - try { - fileIn.close(); - } catch (IOException e) { - Log.w("PartCanvasView", e.getMessage()); - return false; - } - - return true; - } - - private List deserializeObject(JSONObject jsonObject) throws JSONException { - String type = jsonObject.getString("_type"); - - if(type.equals(RoundButton.class.getName())) { - Tuple2> 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 connections) { Map partsById = buildPartsById(); for (PartConnection connection : connections){ @@ -349,38 +270,10 @@ class CanvasView extends View implements PartGrid { } private void saveState() throws IOException { - File filesDir = getContext().getFilesDir(); - FileOutputStream fileOut = new FileOutputStream(filesDir + "/" + name); - - byte[] serialized = serializeState(); - 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(){{ - put("version", 0); - }}); - - final JSONObject state = new JSONObject(new HashMap(){{ - put("metadata", metadataObject); - put("parts", partArray); - }}); - return state.toString().getBytes("UTF-8"); + CardFile cardFile = new CardFile(CardFile.getDefaultCardStorage(getContext())); + cardFile.setName(name); + cardFile.addParts(parts); + cardFile.save(getContext()); } @Nullable diff --git a/app/src/main/java/com/codigoparallevar/minicards/CardActivity.java b/app/src/main/java/com/codigoparallevar/minicards/CardActivity.java index 320fc68..256bf76 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/CardActivity.java +++ b/app/src/main/java/com/codigoparallevar/minicards/CardActivity.java @@ -9,9 +9,12 @@ import android.util.Log; import android.view.MotionEvent; import android.view.View; +import java.io.IOException; + public class CardActivity extends AppCompatActivity { public final static String INTENT = "com.codigoparallevar.minicards.CARD"; + public static final String CARD_PATH_KEY = "CARD_PATH"; CanvasView canvasView; com.getbase.floatingactionbutton.AddFloatingActionButton AddPartButton; @@ -43,7 +46,22 @@ public class CardActivity extends AppCompatActivity { // Use manually controlled canvas canvasView = (CanvasView) findViewById(R.id.canvasView); 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); removePartFab = (FloatingActionButton) findViewById(R.id.remove_part_fab); diff --git a/app/src/main/java/com/codigoparallevar/minicards/CardFile.java b/app/src/main/java/com/codigoparallevar/minicards/CardFile.java new file mode 100644 index 0000000..3b3c814 --- /dev/null +++ b/app/src/main/java/com/codigoparallevar/minicards/CardFile.java @@ -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 parts; + private int backgroundColor; + @NonNull + private List 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 getParts() { + return parts; + } + + @NonNull + public List getConnections() { + return connections; + } + + @NonNull + public String getPath() { + return path; + } + + @NonNull + public CardFile addParts(List 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 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(){{ + put("version", 0); + put("name", name); + put("backgroundColor", serializeColor(backgroundColor)); + }}); + + final JSONObject state = new JSONObject(new HashMap(){{ + 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 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> 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> deserializeObject(PartGrid grid, + JSONObject jsonObject) throws JSONException { + String type = jsonObject.getString("_type"); + + if(type.equals(RoundButton.class.getName())) { + Tuple2> 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.emptyList()); + } + else if (type.equals(ColorBox.class.getName())){ + return new Tuple2<>(ColorBox.deserialize(grid, jsonObject.getJSONObject("_data")), + Collections.emptyList()); + } + else { + throw new JSONException("Expected known class, found " + type); + } + } + + public static String getDefaultCardStorage(Context context) { + return context.getFilesDir().getAbsolutePath() + CardFile.PATH_SEPARATOR + "cards"; + } +} diff --git a/app/src/main/java/com/codigoparallevar/minicards/CardPreviewArrayAdapter.java b/app/src/main/java/com/codigoparallevar/minicards/CardPreviewArrayAdapter.java index bde2739..b4f53f4 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/CardPreviewArrayAdapter.java +++ b/app/src/main/java/com/codigoparallevar/minicards/CardPreviewArrayAdapter.java @@ -28,12 +28,13 @@ class CardPreviewArrayAdapter extends ArrayAdapter { public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 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() { @Override public void onClick(View v) { Intent i = new Intent(CardActivity.INTENT); + i.putExtra(CardActivity.CARD_PATH_KEY, card.getPath()); CardPreviewArrayAdapter.this.getContext().startActivity(i); } }); diff --git a/app/src/main/java/com/codigoparallevar/minicards/DeckPreviewActivity.java b/app/src/main/java/com/codigoparallevar/minicards/DeckPreviewActivity.java index 783c1f4..e3d918e 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/DeckPreviewActivity.java +++ b/app/src/main/java/com/codigoparallevar/minicards/DeckPreviewActivity.java @@ -1,14 +1,19 @@ package com.codigoparallevar.minicards; -import android.graphics.Color; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.View; 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 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); - cardArrayAdapter = new CardPreviewArrayAdapter(getApplicationContext(), new PreviewCard[]{ - new PreviewCard("Default", 0, PreviewCard.DEFAULT_COLOR), - new PreviewCard("Second", 1, Color.parseColor("#FF00FF")), - new PreviewCard("Greenie", 2, Color.parseColor("#00FF00")), - }); + cardArrayAdapter = new CardPreviewArrayAdapter(getApplicationContext(), listAvailableCards()); 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 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()]); + } } diff --git a/app/src/main/java/com/codigoparallevar/minicards/ErrorLoadingCardException.java b/app/src/main/java/com/codigoparallevar/minicards/ErrorLoadingCardException.java new file mode 100644 index 0000000..4bedc05 --- /dev/null +++ b/app/src/main/java/com/codigoparallevar/minicards/ErrorLoadingCardException.java @@ -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} +} diff --git a/app/src/main/java/com/codigoparallevar/minicards/PreviewCard.java b/app/src/main/java/com/codigoparallevar/minicards/PreviewCard.java index 1e198b5..6209cda 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/PreviewCard.java +++ b/app/src/main/java/com/codigoparallevar/minicards/PreviewCard.java @@ -1,28 +1,25 @@ package com.codigoparallevar.minicards; -import android.graphics.Color; - class PreviewCard { - public static final int DEFAULT_COLOR = Color.parseColor("#044563"); private final String name; - private final int cardId; 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.cardId = cardId; this.color = color; + this.path = path; } public String getName() { return name; } - public int getCardId() { - return cardId; - } - public int getColor() { return color; } + + public String getPath() { + return path; + } } diff --git a/app/src/main/java/com/codigoparallevar/minicards/StubPartGrid.java b/app/src/main/java/com/codigoparallevar/minicards/StubPartGrid.java new file mode 100644 index 0000000..c31320a --- /dev/null +++ b/app/src/main/java/com/codigoparallevar/minicards/StubPartGrid.java @@ -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 getCenteredOn() { + return null; + } +} diff --git a/app/src/main/java/com/codigoparallevar/minicards/parts/buttons/RoundButton.java b/app/src/main/java/com/codigoparallevar/minicards/parts/buttons/RoundButton.java index c27d6af..9470f05 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/parts/buttons/RoundButton.java +++ b/app/src/main/java/com/codigoparallevar/minicards/parts/buttons/RoundButton.java @@ -211,6 +211,10 @@ public class RoundButton implements Part { return null; } + public RoundOutputConnector getPressedOutputConnector(){ + return _pressedOuputConnector; + } + public static Tuple2> deserialize(PartGrid partGrid, JSONObject data) throws JSONException { String id = data.getString("id"); int xCenter = data.getInt("x_center"); diff --git a/app/src/main/java/com/codigoparallevar/minicards/parts/samples/ColorBox.java b/app/src/main/java/com/codigoparallevar/minicards/parts/samples/ColorBox.java index 3e08e40..46ec3e7 100644 --- a/app/src/main/java/com/codigoparallevar/minicards/parts/samples/ColorBox.java +++ b/app/src/main/java/com/codigoparallevar/minicards/parts/samples/ColorBox.java @@ -149,6 +149,10 @@ public class ColorBox implements Part { return Collections.emptyList(); } + public SignalInputConnector getToggleInputConnector() { + return _toggleInputConnector; + } + @Override public JSONObject serialize() throws JSONException { JSONObject serialized = new JSONObject();