diff --git a/app/src/main/java/lakeeffect/ca/scoutingserverapp/Action.java b/app/src/main/java/lakeeffect/ca/scoutingserverapp/Action.java new file mode 100644 index 0000000..818c629 --- /dev/null +++ b/app/src/main/java/lakeeffect/ca/scoutingserverapp/Action.java @@ -0,0 +1,28 @@ +package lakeeffect.ca.scoutingserverapp; + +import android.view.View; + +/** + * An action that happens (someone adds a scout, changes properties, etc.) + * + * This is used to undo that action. The actions are stored in a list to be recalled upon if an undo is necessary + */ +public class Action { + //0: start match added + //1: last match added + //2: added scout + //3: removed scout + int type; + + //the scout that this happened to + Scout scout; + + //the view that was modified, added or removed + View view; + + public Action(int type, Scout scout, View view) { + this.type = type; + this.scout = scout; + this.view = view; + } +} diff --git a/app/src/main/java/lakeeffect/ca/scoutingserverapp/MainActivity.java b/app/src/main/java/lakeeffect/ca/scoutingserverapp/MainActivity.java index 3ab7fdf..956a695 100644 --- a/app/src/main/java/lakeeffect/ca/scoutingserverapp/MainActivity.java +++ b/app/src/main/java/lakeeffect/ca/scoutingserverapp/MainActivity.java @@ -31,7 +31,6 @@ import java.io.OutputStreamWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Calendar; import java.util.Set; @@ -69,12 +68,14 @@ public class MainActivity extends AppCompatActivity { //the robots schedules ArrayList> robotSchedule = new ArrayList<>(); - //list of allNames added - ArrayList allNames = new ArrayList<>(); - //the names that have been checked off - ArrayList selectedNames = new ArrayList<>(); + //the list of all scouts + ArrayList allScouts = new ArrayList<>(); //for each selected name, there is an array of assigned robots (0 - 5 per match, -1 being a break) - ArrayList assignedRobot = new ArrayList<>(); + ArrayList assignedRobots = new ArrayList<>(); + + //the last actions that have happened, used to undo actions is necessary + ArrayList pastActions = new ArrayList<>(); + final int PAST_ACTIONS_MAX = 25; @Override protected void onCreate(Bundle savedInstanceState) { @@ -236,18 +237,13 @@ public void onClick(View v) { //load names SharedPreferences namesPreferences = getSharedPreferences("names", MODE_PRIVATE); - String allNamesText = namesPreferences.getString("allNames", ""); - if (!allNamesText.equals("")) { - allNames = new ArrayList<>(Arrays.asList(allNamesText.split(","))); - } - - String selectedNamesText = namesPreferences.getString("selectedNames", ""); + String selectedNamesText = namesPreferences.getString("allScouts", ""); if (!selectedNamesText.equals("")) { String[] selectedNamesArray = selectedNamesText.split(","); for (int i = 0; i < selectedNamesArray.length; i++) { Scout scout = new Scout(selectedNamesArray[i]); - selectedNames.add(scout); + allScouts.add(scout); } } @@ -259,7 +255,7 @@ public void onClick(View v) { //add all start matches String[] scoutStartMatchesArray = selectedNameStartMatchesArray[i].split(";"); for (int s = 0; s < scoutStartMatchesArray.length; s++) { - selectedNames.get(i).startMatches.add(Integer.parseInt(scoutStartMatchesArray[s])); + allScouts.get(i).startMatches.add(Integer.parseInt(scoutStartMatchesArray[s])); } } } @@ -273,7 +269,7 @@ public void onClick(View v) { //add all last matches String[] scoutLastMatchesArray = selectedNameLastMatchesArray[i].split(";"); for (int s = 0; s < scoutLastMatchesArray.length; s++) { - selectedNames.get(i).lastMatches.add(Integer.parseInt(scoutLastMatchesArray[s])); + allScouts.get(i).lastMatches.add(Integer.parseInt(scoutLastMatchesArray[s])); } } } @@ -449,17 +445,15 @@ public void readSchedule() throws IOException { int lineNum = 0; while ((line = br.readLine()) != null) { - if(lineNum > 0) { - String[] robots = line.split(","); - - ArrayList robotNumbers = new ArrayList<>(); + String[] robots = line.split(","); - for (int s = 0; s < robots.length; s++) { - robotNumbers.add(Integer.parseInt(robots[i])); - } + ArrayList robotNumbers = new ArrayList<>(); - robotSchedule.add(robotNumbers); + for (int s = 0; s < robots.length; s++) { + robotNumbers.add(Integer.parseInt(robots[s])); } + + robotSchedule.add(robotNumbers); lineNum ++; } br.close(); @@ -469,7 +463,7 @@ public void readSchedule() throws IOException { //creates the schedule based on the selected usernames //returns null if successful, and error message if not public String createSchedule() { - if (selectedNames.size() < 6) { + if (getSelectedAmount() < 6) { return "You must select at least 6 scouts"; } @@ -478,15 +472,15 @@ public String createSchedule() { ArrayList scoutsOff = new ArrayList<>(); //set assigned robots to correct size - assignedRobot = new ArrayList<>(); - for (int i = 0; i < selectedNames.size(); i++) { - assignedRobot.add(new int[robotSchedule.size()]); + assignedRobots = new ArrayList<>(); + for (int i = 0; i < allScouts.size(); i++) { + assignedRobots.add(new int[robotSchedule.size()]); } //reset assigned robots - for (int i = 0; i < assignedRobot.size(); i++) { - for (int s = 0; s < assignedRobot.get(i).length; s++) { - assignedRobot.get(i)[s] = -1; + for (int i = 0; i < assignedRobots.size(); i++) { + for (int s = 0; s < assignedRobots.get(i).length; s++) { + assignedRobots.get(i)[s] = -1; } } @@ -499,10 +493,10 @@ public String createSchedule() { } //if they are not starting on the first match - if (selectedNames.get(i).getLowestStartMatch() > 0) { - Scout scout = selectedNames.get(i); - selectedNames.remove(scout); - selectedNames.add(scout); + if (allScouts.get(i).getLowestStartMatch() > 0) { + Scout scout = allScouts.get(i); + allScouts.remove(scout); + allScouts.add(scout); //try again i--; //add to the non ready scouts to make sure this is not an infinite loop @@ -510,25 +504,25 @@ public String createSchedule() { continue; } - scoutsOn[i] = new Scout(i, selectedNames.get(i).name); + scoutsOn[i] = new Scout(i, allScouts.get(i).name); } - for (int i = 6; i < selectedNames.size(); i++) { + for (int i = 6; i < allScouts.size(); i++) { //if they are not starting on the first match - if (selectedNames.get(i).getLowestStartMatch() > 0) { + if (allScouts.get(i).getLowestStartMatch() > 0) { continue; } - Scout scout = new Scout(i, selectedNames.get(i).name); + Scout scout = new Scout(i, allScouts.get(i).name); scoutsOff.add(scout); scout.timeOff = 0; } for (int matchNum = 0; matchNum < robotSchedule.size(); matchNum++) { //figure out if some selected names should be added or removed because it is now their start match or last match - for (int i = 0; i < selectedNames.size(); i++) { + for (int i = 0; i < allScouts.size(); i++) { //if it is time to add this scout to the roster and they are not already added - Scout scout = selectedNames.get(i); + Scout scout = allScouts.get(i); boolean existsAtMatch = scout.existsAtMatch(matchNum); if (existsAtMatch && getScout(i, scoutsOff) == -1 && getScout(i, scoutsOn) == -1) { Scout newScout = new Scout(i, scout.name); @@ -604,10 +598,10 @@ public String createSchedule() { //set the schedule for this match for (int i = 0; i < scoutsOn.length; i++) { - assignedRobot.get(scoutsOn[i].id)[matchNum] = i; + assignedRobots.get(scoutsOn[i].id)[matchNum] = i; } for (int i = 0; i < scoutsOff.size(); i++) { - assignedRobot.get(scoutsOff.get(i).id)[matchNum] = -1; + assignedRobots.get(scoutsOff.get(i).id)[matchNum] = -1; } } @@ -641,6 +635,17 @@ public int getScout(String name, ArrayList scouts) { return -1; } + public int getSelectedAmount() { + int selectedAmount = 0; + for (Scout scout : allScouts) { + if (scout.startMatches.size() > 0) { + selectedAmount++; + } + } + + return selectedAmount; + } + //dialog box that lets you edit and deselect names public void openNameEditor() { @@ -660,18 +665,17 @@ public void openNameEditor() { final TextView currentMatchNumber = ((TextView) addName.findViewById(R.id.currentMatchNumber)); //add checkbox and close button for each username - for (int i = 0; i < allNames.size(); i++) { + for (int i = 0; i < allScouts.size(); i++) { final View view = View.inflate(this, R.layout.closable_checkbox, null); CheckBox checkBox = ((CheckBox) view.findViewById(R.id.nameCheckBox)); - final String name = allNames.get(i); + final Scout scout = allScouts.get(i); - checkBox.setText(name); + checkBox.setText(scout.name); //if it is selected, check the box - int index = getScout(name, selectedNames); - if (index != -1 && selectedNames.get(index).existsAtMatch(robotSchedule.size() - 1)) { + if (scout.existsAtMatch(robotSchedule.size() - 1)) { checkBox.setChecked(true); } @@ -679,7 +683,7 @@ public void openNameEditor() { checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - nameClicked(buttonView, isChecked, name, currentMatchNumber); + nameClicked(buttonView, isChecked, scout.name, currentMatchNumber); } }); @@ -689,9 +693,14 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @Override public void onClick(View v) { createdNames.removeView(view); - allNames.remove(name); - if (getScout(name, selectedNames) != -1) { - selectedNames.remove(getScout(name, selectedNames)); + allScouts.remove(scout); + + //a scout was removed + //add the action to the past actions list + pastActions.add(new Action(3, scout, view)); + + if (pastActions.size() > PAST_ACTIONS_MAX) { + pastActions.remove(0); } updateNames(); @@ -715,13 +724,20 @@ public void onClick(View v) { nameEditText.setText(""); //add it to the list - allNames.add(name); + final Scout scout = new Scout(-1, name); + allScouts.add(scout); updateNames(); //add it to the UI final View view = View.inflate(MainActivity.this, R.layout.closable_checkbox, null); + //add the action to the past actions list + pastActions.add(new Action(2, scout, view)); + if (pastActions.size() > PAST_ACTIONS_MAX) { + pastActions.remove(0); + } + CheckBox checkBox = ((CheckBox) view.findViewById(R.id.nameCheckBox)); checkBox.setText(name); @@ -739,9 +755,13 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @Override public void onClick(View v) { createdNames.removeView(view); - allNames.remove(name); - if (getScout(name, selectedNames) != -1) { - selectedNames.remove(getScout(name, selectedNames)); + allScouts.remove(scout); + + //a scout was removed + //add the action to the past actions list + pastActions.add(new Action(3, scout, view)); + if (pastActions.size() > PAST_ACTIONS_MAX) { + pastActions.remove(0); } updateNames(); @@ -752,6 +772,48 @@ public void onClick(View v) { } }); + //setup undo button + addName.findViewById(R.id.undoPastAction).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //create confirmation dialog + new AlertDialog.Builder(MainActivity.this) + .setTitle("Are you sure you would like to undo?") + .setNegativeButton("No", null) + .setPositiveButton("Yes", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //get last action + Action undoAction = pastActions.get(pastActions.size() - 1); + + switch (undoAction.type) { + case 0: + undoAction.scout.startMatches.remove(undoAction.scout.startMatches.size() - 1); + undoAction.scout.undo = true; + ((CheckBox) undoAction.view).setChecked(false); + break; + case 1: + undoAction.scout.lastMatches.remove(undoAction.scout.lastMatches.size() - 1); + undoAction.scout.undo = true; + ((CheckBox) undoAction.view).setChecked(true); + break; + case 2: + allScouts.remove(undoAction.scout); + createdNames.removeView(undoAction.view); + break; + case 3: + allScouts.add(undoAction.scout); + createdNames.addView(undoAction.view); + } + + //remove past action from list now + pastActions.remove(undoAction); + } + }) + .show(); + } + }); + fullView.addView(addName); fullScrollView.addView(fullView); @@ -766,14 +828,26 @@ public void onClick(View v) { //called when a name is checked or unchecked public void nameClicked(CompoundButton checkbox, boolean isChecked, String name, TextView currentMatchNumber) { - int scoutIndex = getScout(name, selectedNames); + int scoutIndex = getScout(name, allScouts); int matchNum = Integer.parseInt(currentMatchNumber.getText().toString()) - 1; + //the checkbox was programmatically checked, no need to check anything + if (scoutIndex != -1 && allScouts.get(scoutIndex).undo) { + allScouts.get(scoutIndex).undo = false; + return; + } + if (isChecked) { if (scoutIndex == -1) { - selectedNames.add(new Scout(name, matchNum)); + allScouts.add(new Scout(name, matchNum)); + + runOnUiThread(new Thread() { + public void run() { + Toast.makeText(MainActivity.this, "This should not happen!!!", Toast.LENGTH_SHORT).show(); + } + }); } else { - Scout scout = selectedNames.get(scoutIndex); + Scout scout = allScouts.get(scoutIndex); if (scout.existsAtMatch(matchNum)) { //this action should not happen, this is an invalid time @@ -788,10 +862,16 @@ public void run() { //enable them at that match number scout.startMatches.add(matchNum); + + //add the action to the past actions list + pastActions.add(new Action(0, scout, checkbox)); + if (pastActions.size() > PAST_ACTIONS_MAX) { + pastActions.remove(0); + } } } else { if (scoutIndex != -1) { - Scout scout = selectedNames.get(scoutIndex); + Scout scout = allScouts.get(scoutIndex); if (!scout.existsAtMatch(matchNum)) { //this action should not happen, this is an invalid time @@ -817,6 +897,15 @@ public void run() { //undo as an error has been caused scout.lastMatches.remove(scout.lastMatches.size() - 1); checkbox.setChecked(true); + } else { + //it was successful + + //add the action to the past actions list + pastActions.add(new Action(1, scout, checkbox)); + + if (pastActions.size() > PAST_ACTIONS_MAX) { + pastActions.remove(0); + } } } } @@ -830,9 +919,9 @@ public void updateNames() { //get all the names in csv String names = ""; - for (String name : allNames) { - names += name; - if (allNames.indexOf(name) < allNames.size() - 1) { + for (Scout scout : allScouts) { + names += scout.name; + if (allScouts.indexOf(scout) < allScouts.size() - 1) { names += ","; } } @@ -844,7 +933,7 @@ public void updateNames() { String allSelectedNames = ""; String allSelectedNameStartMatches = ""; String allSelectedNameLastMatches = ""; - for (Scout scout : selectedNames) { + for (Scout scout : allScouts) { allSelectedNames += scout.name; for (int i = 0; i < scout.startMatches.size(); i++) { allSelectedNameStartMatches += scout.startMatches.get(i); @@ -859,13 +948,13 @@ public void updateNames() { } } - if (selectedNames.indexOf(scout) < selectedNames.size() - 1) { + if (allScouts.indexOf(scout) < allScouts.size() - 1) { allSelectedNames += ","; allSelectedNameStartMatches += ","; allSelectedNameLastMatches += ","; } } - editor.putString("selectedNames", allSelectedNames); + editor.putString("allScouts", allSelectedNames); editor.putString("selectedNameStartMatches", allSelectedNameStartMatches); editor.putString("selectedNameLastMatches", allSelectedNameLastMatches); diff --git a/app/src/main/java/lakeeffect/ca/scoutingserverapp/PullDataThread.java b/app/src/main/java/lakeeffect/ca/scoutingserverapp/PullDataThread.java index f69cea0..31a1260 100644 --- a/app/src/main/java/lakeeffect/ca/scoutingserverapp/PullDataThread.java +++ b/app/src/main/java/lakeeffect/ca/scoutingserverapp/PullDataThread.java @@ -10,6 +10,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Arrays; import java.util.UUID; @@ -76,8 +77,9 @@ public void run() { } }); - out.write("REQUEST LABELS".getBytes(Charset.forName("UTF-8"))); + out.write("REQUEST LABELSEND".getBytes(Charset.forName("UTF-8"))); String labels = waitForMessage(); + labels = labels.substring(0, labels.length() - 3); int version = Integer.parseInt(labels.split(":::")[0]); if(version >= mainActivity.minVersionNum){ @@ -94,16 +96,78 @@ public void run(){ } } + //send schedule + { + mainActivity.runOnUiThread(new Thread() { + public void run() { + mainActivity.status.setText("Connected! Sending schedule to " + device.getName() + "..."); + } + }); + + //the string that will contain the scout schedule data to send to the client + StringBuilder scheduleMessage = new StringBuilder("SEND SCHEDULE:::"); + + for (int scoutIndex = 0; scoutIndex < mainActivity.assignedRobots.size(); scoutIndex++) { + scheduleMessage.append(mainActivity.allScouts.get(scoutIndex).name); + + //add separator + scheduleMessage.append(":"); + + for (int i = 0 ; i < mainActivity.assignedRobots.get(scoutIndex).length; i++) { + scheduleMessage.append(mainActivity.assignedRobots.get(scoutIndex)[i]); + + if (i < mainActivity.assignedRobots.get(scoutIndex).length - 1) { + //add a comma, it's not the last item + scheduleMessage.append(","); + } + } + + if (scoutIndex < mainActivity.assignedRobots.size() - 1) { + //add a separator, it's not the last item + scheduleMessage.append("::"); + } + } + + //send the robot schedule + scheduleMessage.append(":::"); + + for (int i = 0; i < mainActivity.robotSchedule.size(); i++) { + for (int s = 0; s < mainActivity.robotSchedule.get(i).size(); s++) { + scheduleMessage.append(mainActivity.robotSchedule.get(i).get(s)); + + if (s < mainActivity.robotSchedule.get(i).size() - 1) { + //add a comma, it's not the last item + scheduleMessage.append(","); + } + } + if (i < mainActivity.robotSchedule.size() - 1) { + //add a separator, it's not the last item + scheduleMessage.append("::"); + } + } + + //this message has finished + scheduleMessage.append("END"); + + out.write(scheduleMessage.toString().getBytes(Charset.forName("UTF-8"))); + String receivedMessage = waitForMessage(); + + if (!receivedMessage.contains("RECEIVED")) { + //did not succeed, try again + error = true; + } + } + mainActivity.runOnUiThread(new Thread() { public void run() { mainActivity.status.setText("Connected! Requesting Data from " + device.getName() + "..."); } }); - out.write("REQUEST DATA".getBytes(Charset.forName("UTF-8"))); + out.write("REQUEST DATAEND".getBytes(Charset.forName("UTF-8"))); String message = waitForMessage(); - message = message.substring(0, message.length() - 1); + message = message.substring(0, message.length() - 3); mainActivity.runOnUiThread(new Thread() { public void run() { @@ -121,7 +185,7 @@ public void run(){ }); running = false; return; - }else{ + } else { String[] data = message.split(":::")[1].split("::"); if(data[0].equals("nodata")){ @@ -148,7 +212,7 @@ public void run(){ } - out.write("RECEIVED".getBytes(Charset.forName("UTF-8"))); + out.write("RECEIVEDEND".getBytes(Charset.forName("UTF-8"))); } catch (IOException e) { e.printStackTrace(); @@ -218,9 +282,8 @@ public String waitForMessage(){ else continue; String message = finalMessage + new String(bytes, Charset.forName("UTF-8")); - if(!message.endsWith("\n")){ + if(!message.endsWith("END")){ finalMessage = message; - System.out.println(finalMessage + " message"); continue; } diff --git a/app/src/main/java/lakeeffect/ca/scoutingserverapp/Scout.java b/app/src/main/java/lakeeffect/ca/scoutingserverapp/Scout.java index 3731257..739a199 100644 --- a/app/src/main/java/lakeeffect/ca/scoutingserverapp/Scout.java +++ b/app/src/main/java/lakeeffect/ca/scoutingserverapp/Scout.java @@ -22,6 +22,9 @@ public class Scout { //inclusive ArrayList lastMatches = new ArrayList<>(); + //if this just had an undo applied, used to ignore onCheckChange listeners + boolean undo; + public Scout(int id, String name) { this.id = id; this.name = name; @@ -55,6 +58,11 @@ public int getLowestStartMatch() { //does this scout exist at this match public boolean existsAtMatch(int matchNum) { + //they were never selected if this is true + if (startMatches.size() == 0) { + return false; + } + for (int i = 0; i < startMatches.size(); i++) { if (startMatches.get(i) <= matchNum) { //has it been closed since then diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1ea4fcd..29c44c4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -123,10 +123,10 @@ android:id="@+id/timeOffText"/> @@ -146,9 +146,9 @@