From 6c79dd52feead16c2b1969893de17051a287e79a Mon Sep 17 00:00:00 2001 From: WillDavies Date: Sun, 11 Mar 2018 20:08:29 -0500 Subject: [PATCH] Release 4.4.0 - Pre build --- app/build.gradle | 4 +- .../java/com/cpjd/roblu/csv/CSVActivity.java | 2 + .../com/cpjd/roblu/csv/ExportCSVTask.java | 16 +- .../cpjd/roblu/csv/csvSheets/FieldData.java | 87 +++++++++ .../cpjd/roblu/csv/csvSheets/MatchData.java | 2 + app/src/main/java/com/cpjd/roblu/io/IO.java | 99 ++++------ .../java/com/cpjd/roblu/models/RTeam.java | 3 +- .../roblu/models/metrics/RCalculation.java | 2 +- .../cpjd/roblu/models/metrics/RFieldData.java | 58 ++++++ .../cpjd/roblu/models/metrics/RGallery.java | 18 -- .../cpjd/roblu/models/metrics/RMetric.java | 3 +- .../java/com/cpjd/roblu/sync/SyncHelper.java | 30 ++- .../cpjd/roblu/sync/bluetooth/BTServer.java | 25 ++- .../cpjd/roblu/sync/cloud/EventDepacker.java | 2 + .../com/cpjd/roblu/sync/cloud/InitPacker.java | 19 +- .../com/cpjd/roblu/sync/cloud/Service.java | 4 +- .../{utils => sync/qr}/CheckoutEncoder.java | 8 +- .../java/com/cpjd/roblu/sync/qr/QrReader.java | 3 +- .../roblu/tba/ManualScheduleImporter.java | 161 +++++++++++++++ .../java/com/cpjd/roblu/tba/SyncTBAEvent.java | 183 ++++++++++++++++++ .../com/cpjd/roblu/tba/UnpackTBAEvent.java | 125 +++++------- .../ui/events/EventCreateMethodPicker.java | 1 + .../roblu/ui/events/EventDrawerManager.java | 2 +- .../com/cpjd/roblu/ui/events/EventEditor.java | 2 +- .../cpjd/roblu/ui/events/EventSettings.java | 98 +++++++++- .../com/cpjd/roblu/ui/forms/FormViewer.java | 8 + .../com/cpjd/roblu/ui/forms/MetricEditor.java | 6 +- .../com/cpjd/roblu/ui/forms/RMetricToUI.java | 61 ++++++ .../com/cpjd/roblu/ui/images/Drawing.java | 4 +- .../FullScreenImageGalleryActivity.java | 4 +- .../roblu/ui/images/ImageGalleryActivity.java | 13 ++ .../cpjd/roblu/ui/settings/AdvSettings.java | 103 +++++++++- .../cpjd/roblu/ui/team/fragments/Match.java | 2 + .../roblu/ui/team/fragments/Overview.java | 3 +- .../ui/team/fragments/TeamTabAdapter.java | 4 +- .../cpjd/roblu/ui/teams/LoadTeamsTask.java | 3 + .../com/cpjd/roblu/ui/teams/TeamsView.java | 14 +- .../ui/teamsSorting/MetricSortFragment.java | 68 ++++++- .../ui/teamsSorting/TeamMetricProcessor.java | 14 +- .../com/cpjd/roblu/ui/tutorials/Tutorial.java | 1 - .../java/com/cpjd/roblu/utils/Constants.java | 2 +- .../main/java/com/cpjd/roblu/utils/Utils.java | 14 ++ .../main/res/drawable-xxhdpi/row_border.xml | 8 + app/src/main/res/layout/activity_csv.xml | 10 +- app/src/main/res/layout/activity_setup.xml | 3 + app/src/main/res/layout/submetric_import.xml | 31 +++ app/src/main/res/xml/event_preferences.xml | 7 + app/src/main/res/xml/preferences.xml | 8 + 48 files changed, 1125 insertions(+), 223 deletions(-) create mode 100644 app/src/main/java/com/cpjd/roblu/csv/csvSheets/FieldData.java create mode 100644 app/src/main/java/com/cpjd/roblu/models/metrics/RFieldData.java rename app/src/main/java/com/cpjd/roblu/{utils => sync/qr}/CheckoutEncoder.java (97%) create mode 100644 app/src/main/java/com/cpjd/roblu/tba/ManualScheduleImporter.java create mode 100644 app/src/main/java/com/cpjd/roblu/tba/SyncTBAEvent.java create mode 100644 app/src/main/res/drawable-xxhdpi/row_border.xml create mode 100644 app/src/main/res/layout/submetric_import.xml diff --git a/app/build.gradle b/app/build.gradle index 387c09c..c870119 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "com.cpjd.roblu" minSdkVersion 19 targetSdkVersion 27 - versionCode 50 - versionName "4.3.5" + versionCode 52 + versionName "4.4.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" javaCompileOptions { diff --git a/app/src/main/java/com/cpjd/roblu/csv/CSVActivity.java b/app/src/main/java/com/cpjd/roblu/csv/CSVActivity.java index 9a9e2ff..eec759f 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/CSVActivity.java +++ b/app/src/main/java/com/cpjd/roblu/csv/CSVActivity.java @@ -73,6 +73,7 @@ public boolean onOptionsItemSelected(MenuItem item) { CheckBox matchList = findViewById(R.id.match_list); CheckBox matchLookup = findViewById(R.id.match_lookup); CheckBox ourMatches = findViewById(R.id.our_matches); + CheckBox fieldData = findViewById(R.id.field_data); ArrayList enabledSheets = new ArrayList<>(); if(matchData.isChecked()) enabledSheets.add(ExportCSVTask.SHEETS.MATCH_DATA); @@ -80,6 +81,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if(matchList.isChecked()) enabledSheets.add(ExportCSVTask.SHEETS.MATCH_LIST); if(matchLookup.isChecked()) enabledSheets.add(ExportCSVTask.SHEETS.MATCH_LOOKUP); if(ourMatches.isChecked()) enabledSheets.add(ExportCSVTask.SHEETS.OUR_MATCHES); + if(fieldData.isChecked()) enabledSheets.add(ExportCSVTask.SHEETS.FIELD_DATA); String fileName = ((AppCompatEditText)findViewById(R.id.file_name)).getText().toString(); diff --git a/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java b/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java index 4c4dc12..0e89766 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java +++ b/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java @@ -5,6 +5,7 @@ import android.util.Log; import com.cpjd.roblu.csv.csvSheets.CSVSheet; +import com.cpjd.roblu.csv.csvSheets.FieldData; import com.cpjd.roblu.csv.csvSheets.Lookup; import com.cpjd.roblu.csv.csvSheets.MatchData; import com.cpjd.roblu.csv.csvSheets.MatchList; @@ -53,7 +54,7 @@ public class ExportCSVTask extends Thread { * The order of this array should match the order of the IDs in * @see SHEETS */ - private CSVSheet[] CSVSheets = {new MatchData(), new PitData(), new MatchList(), new Lookup(), new OurMatches()}; + private CSVSheet[] CSVSheets = {new MatchData(), new PitData(), new MatchList(), new Lookup(), new OurMatches(), new FieldData()}; /** * Reference to the context object for file system access @@ -113,6 +114,7 @@ public static class SHEETS { static int MATCH_LIST = 2; static int MATCH_LOOKUP = 3; static int OUR_MATCHES = 4; + static int FIELD_DATA = 5; } public static class VERBOSENESS { @@ -254,18 +256,18 @@ public void run() { new Thread() { public void run() { if(s.isEnabled()) { - // try { + try { s.setIo(io); s.setVerboseness(verboseness); s.setWorkbook(workbook); Log.d("RBS", "ExportCSVTask: Generating sheet: "+s.getSheetName()); s.setCellStyle(BorderStyle.THIN, IndexedColors.WHITE, IndexedColors.BLACK, false); // sets the default, this may get overrided at any point in time by the user s.generateSheet(sheets.get(s.getSheetName()), event, form, teams, checkouts); - // for(int i = 0; i < sheets.get(s.getSheetName()).getRow(0).getLastCellNum(); i++) sheets.get(s.getSheetName()).setColumnWidth(i, s.getColumnWidth()); - // } catch(Exception e) { - // listener.errorOccurred("Failed to execute "+s.getSheetName()+" sheet generation."); - // Log.d("RBS", "Failed to execute "+s.getSheetName()+" sheet generation. Err: "+e.getMessage()); - // } + for(int i = 0; i < sheets.get(s.getSheetName()).getRow(0).getLastCellNum(); i++) sheets.get(s.getSheetName()).setColumnWidth(i, s.getColumnWidth()); + } catch(Exception e) { + listener.errorOccurred("Failed to execute "+s.getSheetName()+" sheet generation."); + Log.d("RBS", "Failed to execute "+s.getSheetName()+" sheet generation. Err: "+e.getMessage()); + } threadCompleted(s.getSheetName()); } diff --git a/app/src/main/java/com/cpjd/roblu/csv/csvSheets/FieldData.java b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/FieldData.java new file mode 100644 index 0000000..89b3046 --- /dev/null +++ b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/FieldData.java @@ -0,0 +1,87 @@ +package com.cpjd.roblu.csv.csvSheets; + +import com.cpjd.roblu.models.RCheckout; +import com.cpjd.roblu.models.REvent; +import com.cpjd.roblu.models.RForm; +import com.cpjd.roblu.models.RTab; +import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RFieldData; +import com.cpjd.roblu.models.metrics.RMetric; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xssf.usermodel.XSSFSheet; + +import java.util.ArrayList; + +/** + * Exports all the field data metrics + * + * @author Will Davies + * @version 1 + * @since 4.4.0 + */ +public class FieldData extends CSVSheet { + @Override + public void generateSheet(XSSFSheet sheet, REvent event, RForm form, RTeam[] teams, ArrayList checkouts) { + Row one = createRow(sheet); + createCell(one, 0, "Team#"); + createCell(one, 1, "Match#"); + + // Find a random RFieldData reference + RFieldData fieldData = null; + try { + mainLoop: + for(RTab tab : teams[0].getTabs()) { + if(tab.getTitle().equalsIgnoreCase("PIT") || tab.getTitle().equalsIgnoreCase("PREDICTIONS")) continue; + for(RMetric metric2 : tab.getMetrics()) { + if(metric2 instanceof RFieldData) { + fieldData = (RFieldData) metric2; + break mainLoop; + } + } + } + } catch(Exception e) {//} + } + + // Copy the metrics over + int index = 2; + for(Object key : fieldData.getData().keySet()) { + createCell(one, index, key.toString()); + index++; + } + + // Start copying data + for(RCheckout checkout : checkouts) { + if(!checkout.getTeam().getTabs().get(0).getTitle().startsWith("Quals")) continue; + + Row row = createRow(sheet); + + createCell(row, 0, String.valueOf(checkout.getTeam().getNumber())); + createCell(row, 1, checkout.getTeam().getTabs().get(0).getTitle()); + + index = 0; + mainLoop : for(RTab tab : checkout.getTeam().getTabs()) { + for(RMetric metric2 : tab.getMetrics()) { + if(metric2 instanceof RFieldData) { + for(Object key : ((RFieldData)metric2).getData().keySet()) { + createCell(row, index + 2, ((RFieldData)metric2).getData().get(key).toString()); + index++; + } + break mainLoop; + } + } + } + + } + } + + @Override + public String getSheetName() { + return "FieldData"; + } + + @Override + public int getColumnWidth() { + return 3000; + } +} diff --git a/app/src/main/java/com/cpjd/roblu/csv/csvSheets/MatchData.java b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/MatchData.java index 09fdb92..fff23ba 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/csvSheets/MatchData.java +++ b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/MatchData.java @@ -4,6 +4,7 @@ import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.models.metrics.RStopwatch; @@ -45,6 +46,7 @@ public void generateSheet(XSSFSheet sheet, REvent event, RForm form, RTeam[] tea for(RMetric metric : checkout.getTeam().getTabs().get(0).getMetrics()) { if(shouldWriteMetric(checkout.getTeam(), metric)) { if(metric instanceof RStopwatch) createCell(data, index + 2, ((RStopwatch) metric).getLapsString()); + else if(metric instanceof RFieldData) continue; else createCell(data, index + 2, metric.toString()); } else createCell(data, index + 2, ""); diff --git a/app/src/main/java/com/cpjd/roblu/io/IO.java b/app/src/main/java/com/cpjd/roblu/io/IO.java index 41315a5..d2cabb7 100644 --- a/app/src/main/java/com/cpjd/roblu/io/IO.java +++ b/app/src/main/java/com/cpjd/roblu/io/IO.java @@ -8,10 +8,10 @@ import com.cpjd.roblu.models.RBackup; import com.cpjd.roblu.models.RCheckout; -import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RSettings; +import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.RTeam; import com.cpjd.roblu.models.RUI; import com.cpjd.roblu.utils.Utils; @@ -396,7 +396,10 @@ public RForm loadForm(int eventID) { * @param form the form to save */ public void saveForm(int eventID, RForm form) { - if(eventID == -1) serializeObject(form, new File(context.getFilesDir(), PREFIX+File.separator+"master_form.ser")); + if(eventID == -1) { + serializeObject(form, new File(context.getFilesDir(), PREFIX+File.separator+"master_form.ser")); + return; + } serializeObject(form, new File(context.getFilesDir(), PREFIX+File.separator+"events"+File.separator+eventID+File.separator+"form.ser")); } @@ -405,15 +408,14 @@ public void saveForm(int eventID, RForm form) { /* * Backup methods */ - /** * Saves a backup file to the cache directory. It should be saved to an external location by the user * IMMEDIATELY, because the cache dir does not guarantee the existence of any files * @param backup the backup file to save to a temporary file * @return File reference to a temporary file location that can later be saved to an external location */ - public File saveBackup(RBackup backup) { - File file = new File(context.getCacheDir(), PREFIX+File.separator+"backups"+ File.separator+"event.roblubackup"); + public File saveBackup(RBackup backup, String name) { + File file = new File(context.getCacheDir(), PREFIX+File.separator+"backups"+ File.separator+name); if(file.mkdirs()) Log.d("RBS", "Successfully created backup parent dirs"); if(file.exists()) delete(file); serializeObject(backup, file); @@ -426,7 +428,7 @@ public File saveBackup(RBackup backup) { * @return RBackup object instance */ public RBackup convertBackupFile(Uri toCopy) { - File file = new File(context.getCacheDir(), PREFIX+ File.separator+"tempBackupImport.roblubackup"); + File file = new File(context.getCacheDir(), PREFIX+ File.separator+"tempBackup.backup"); if(file.mkdirs()) Log.d("RBS", "Successfully created backup parent dirs"); if(file.exists()) { if(!file.delete()) Log.d("RBS", "Failed to delete old cached backup file."); @@ -444,11 +446,35 @@ public RBackup convertBackupFile(Uri toCopy) { } return null; } catch(Exception e) { + Log.d("RBS", "Exception: "+e.getMessage()); return null; } finally { if(file.delete()) Log.d("RBS", "Cached backup file successfully deleted."); } } + + public RForm convertFormFile(Uri toCopy) { + File file = new File(context.getFilesDir(), PREFIX+ File.separator+"master_form.ser"); + if(file.mkdirs()) Log.d("RBS", "Successfully created form backup parent dirs"); + if(file.exists()) { + if(!file.delete()) Log.d("RBS", "Failed to delete old cached backup file."); + } + try { + InputStream is = context.getContentResolver().openInputStream(toCopy); + FileOutputStream out = new FileOutputStream(file); + if(is != null) { + IOUtils.copy(is, out); + RForm backup = (RForm) deserializeObject(file); + is.close(); + out.flush(); + out.close(); + } + return (RForm) deserializeObject(file); + } catch(Exception e) { + Log.d("RBS", "Exception: "+e.getMessage()); + return null; + } + } // End backup methods /* @@ -472,61 +498,6 @@ public File getNewCSVExportFile(String name) { return f; } // End CSV Methods - - /* - * CHECKOUTS Methods - */ - /** - * Saves a checkout to the /checkouts/ directory, presumably because an import has occured - * @param checkout the RCheckout object instance to save - */ - public void saveCheckout(RCheckout checkout) { - serializeObject(checkout, new File(context.getFilesDir(), PREFIX+File.separator+"checkouts"+File.separator+checkout.getID()+".ser")); - } - - /** - * Loads the RCheckout with the specified ID - * @param checkoutID the checkout ID to laod - * @return RCheckout object instance - */ - private RCheckout loadCheckout(int checkoutID) { - RCheckout checkout = (RCheckout) deserializeObject(new File(context.getFilesDir(), PREFIX+File.separator+"checkouts"+File.separator+checkoutID+".ser")); - if(checkout != null) checkout.setID(checkoutID); - return checkout; - } - - /** - * Loads all checkouts in the file system - * @return Array of RCheckout object instances - */ - public RCheckout[] loadCheckouts() { - File[] files = getChildFiles(new File(context.getFilesDir(), PREFIX+File.separator+"checkouts"+File.separator)); - if(files == null || files.length == 0) return null; - RCheckout[] checkouts = new RCheckout[files.length]; - for(int i = 0; i < checkouts.length; i++) { - checkouts[i] = loadCheckout(Integer.parseInt(files[i].getName().replace(".ser", ""))); - } - return checkouts; - } - - /** - * Returns an unused, new event ID that a new team can be saved under. - * This method will take the HIGHEST ID it finds, and add one to it. It will - * not just find the closest unused ID to 0. - * @return unused ID to save this team's info to - */ - public int getNewCheckoutID() { - File[] files = getChildFiles(new File(context.getFilesDir(), PREFIX+ File.separator+"checkouts"+File.separator)); - if(files == null || files.length == 0) return 0; - int topID = 0; - for(File f : files) { - int newID = Integer.parseInt(f.getName().replaceAll(".ser", "")); - if(newID > topID) topID = newID; - } - return topID + 1; - } - // End checkouts methods - /* * PENDING methods */ @@ -535,13 +506,13 @@ public int getNewCheckoutID() { * Saves a checkout to the /pending/ directory, presumably because an import has occurred * @param checkout the RCheckout instance to save */ - public void savePendingObject(RCheckout checkout) { + public void savePendingCheckout(RCheckout checkout) { serializeObject(checkout, new File(context.getFilesDir(), PREFIX+File.separator+"pending"+File.separator+checkout.getID()+".ser")); } /** * Loads the RCheckout with the specified ID - * @param checkoutID the checkout ID to laod + * @param checkoutID the checkout ID to load * @return RCheckout object instance */ public RCheckout loadPendingCheckout(int checkoutID) { @@ -559,7 +530,7 @@ public RCheckout[] loadPendingCheckouts() { if(files == null || files.length == 0) return null; RCheckout[] checkouts = new RCheckout[files.length]; for(int i = 0; i < checkouts.length; i++) { - checkouts[i] = loadCheckout(Integer.parseInt(files[i].getName().replace(".ser", ""))); + checkouts[i] = loadPendingCheckout(Integer.parseInt(files[i].getName().replace(".ser", ""))); } return checkouts; } diff --git a/app/src/main/java/com/cpjd/roblu/models/RTeam.java b/app/src/main/java/com/cpjd/roblu/models/RTeam.java index 46de316..38eede1 100644 --- a/app/src/main/java/com/cpjd/roblu/models/RTeam.java +++ b/app/src/main/java/com/cpjd/roblu/models/RTeam.java @@ -237,8 +237,7 @@ public void verify(RForm form) { } } } - - // Update default values for non-modified values, also check for some weird scenarios + // Update default values for non-modified values, also check for some weird scenario temp = form.getPit(); for(int i = 0; i < tabs.size(); i++) { if(!tabs.get(i).getTitle().equalsIgnoreCase("PIT")) temp = form.getMatch(); diff --git a/app/src/main/java/com/cpjd/roblu/models/metrics/RCalculation.java b/app/src/main/java/com/cpjd/roblu/models/metrics/RCalculation.java index 16e0755..3608407 100644 --- a/app/src/main/java/com/cpjd/roblu/models/metrics/RCalculation.java +++ b/app/src/main/java/com/cpjd/roblu/models/metrics/RCalculation.java @@ -96,7 +96,7 @@ else if(metric instanceof RCalculation) { // Process lastValue = Utils.round(new DoubleEvaluator().evaluate(equation), 2); - return String.valueOf(Utils.round(new DoubleEvaluator().evaluate(equation), 2)); + return String.valueOf(lastValue); } catch(Exception e) { Log.d("RBS", "bad equation: ", e); return "Bad equation"; diff --git a/app/src/main/java/com/cpjd/roblu/models/metrics/RFieldData.java b/app/src/main/java/com/cpjd/roblu/models/metrics/RFieldData.java new file mode 100644 index 0000000..597b437 --- /dev/null +++ b/app/src/main/java/com/cpjd/roblu/models/metrics/RFieldData.java @@ -0,0 +1,58 @@ +package com.cpjd.roblu.models.metrics; + +import org.codehaus.jackson.annotate.JsonTypeName; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * RFieldDiagram stores match data from TheBlueAlliance.com, this metric is always uneditable + */ +@EqualsAndHashCode(callSuper = true) +@Data +@JsonTypeName("RFieldData") +public class RFieldData extends RMetric { + + /** + * Changing this versionUID will render this class incompatible with older versions. + */ + public static final long serialVersionUID = 1L; + + /** + * The string is the metric name, the ArrayList stores 2 metrics (red first, blue second) + * representing the match data. + */ + private LinkedHashMap> data; + + public RFieldData() {} + + public RFieldData(int ID, String title) { + super(ID, title); + } + + @Override + public boolean isModified() { + return true; + } + + @Override + public String getFormDescriptor() { + return "Type: Field data"; + } + + @Override + public RMetric clone() { + RFieldData fieldData = new RFieldData(ID, title); + fieldData.setData(data); + return fieldData; + } + + @Override + public String toString() { + return ""; + } + +} diff --git a/app/src/main/java/com/cpjd/roblu/models/metrics/RGallery.java b/app/src/main/java/com/cpjd/roblu/models/metrics/RGallery.java index 4df2a98..1f333e2 100644 --- a/app/src/main/java/com/cpjd/roblu/models/metrics/RGallery.java +++ b/app/src/main/java/com/cpjd/roblu/models/metrics/RGallery.java @@ -55,24 +55,6 @@ public RGallery(int ID, String title) { super(ID, title); } - /** - * Adds a new image to the image array - * @param image a byte[] representing the image, format must match that UI requirements - */ - public void addImage(byte[] image) { - if(images == null) images = new ArrayList<>(); - - images.add(image); - } - - /** - * Removes an image from the image array - * @param position the index position of the image to be removed - */ - public void removeImage(int position) { - if(images != null) images.remove(position); - } - /** * Returns the image array, ensuring that it's not null * @return array containing all images diff --git a/app/src/main/java/com/cpjd/roblu/models/metrics/RMetric.java b/app/src/main/java/com/cpjd/roblu/models/metrics/RMetric.java index e455f82..745144c 100644 --- a/app/src/main/java/com/cpjd/roblu/models/metrics/RMetric.java +++ b/app/src/main/java/com/cpjd/roblu/models/metrics/RMetric.java @@ -33,7 +33,8 @@ @JsonSubTypes.Type(value = RTextfield.class, name = "RTextfield"), @JsonSubTypes.Type(value = RDivider.class, name = "RDivider"), @JsonSubTypes.Type(value = RFieldDiagram.class, name = "RFieldDiagram"), - @JsonSubTypes.Type(value = RCalculation.class, name = "RCalculation") + @JsonSubTypes.Type(value = RCalculation.class, name = "RCalculation"), + @JsonSubTypes.Type(value = RFieldData.class, name = "RFieldData") }) public abstract class RMetric implements Serializable { /** diff --git a/app/src/main/java/com/cpjd/roblu/sync/SyncHelper.java b/app/src/main/java/com/cpjd/roblu/sync/SyncHelper.java index 0ee33ca..9535e91 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/SyncHelper.java +++ b/app/src/main/java/com/cpjd/roblu/sync/SyncHelper.java @@ -11,6 +11,8 @@ import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RTab; import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RCalculation; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.notifications.Notify; @@ -69,7 +71,6 @@ public SyncHelper(IO io, REvent activeEvent, MODES mode) { mapper = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); } - /** * Packages a list of checkouts and converts them to a string * @param checkouts the checkouts to package @@ -87,10 +88,15 @@ public String packCheckouts(ArrayList checkouts) throws Exception { /* * Pack images into the checkouts + * (and wipe data from any RFieldData metrics) */ for(RCheckout checkout : checkouts) { for(RTab tab : checkout.getTeam().getTabs()) { for(int i = 0; tab.getMetrics() != null && i < tab.getMetrics().size(); i++) { + if(tab.getMetrics().get(i) instanceof RFieldData) { + ((RFieldData) tab.getMetrics().get(i)).setData(null); + } + if(!(tab.getMetrics().get(i) instanceof RGallery)) continue; // Make sure the array is not null. @@ -135,9 +141,9 @@ public void unpackCheckouts(CloudCheckout[] checkouts, RSyncSettings cloudSettin // Update the sync IDs cloudSettings.getCheckoutSyncIDs().put(checkout.getID(), serial.getSyncID()); } - + // Flag for uploading else if(mode == MODES.BLUETOOTH) { - io.savePendingObject(checkout); + io.savePendingCheckout(checkout); } } catch(Exception e) { Log.d("RBS", "Failed to unpack checkout: "+serial); @@ -168,8 +174,8 @@ public void mergeCheckout(RCheckout checkout) { // The team was found, so do a merge if(team != null) { team.verify(form); - team.setLastEdit(checkout.getTeam().getLastEdit()); + boolean shouldOverrideLastEdited = false; for(RTab downloadedTab : checkout.getTeam().getTabs()) { boolean matchLocated = false; for(RTab localTab : team.getTabs()) { @@ -183,9 +189,14 @@ public void mergeCheckout(RCheckout checkout) { if(downloadedTab.getEdits() != null) localTab.setEdits(downloadedTab.getEdits()); for(RMetric downloadedMetric : downloadedTab.getMetrics()) { + if(!(downloadedMetric instanceof RCalculation) && !(downloadedMetric instanceof RFieldData) && downloadedMetric.isModified()) shouldOverrideLastEdited = true; + for(RMetric localMetric : localTab.getMetrics()) { // Found the metric, determine if a merge needs to occur if(downloadedMetric.getID() == localMetric.getID()) { + // Ignore imports from this metric + if(downloadedMetric instanceof RFieldData) break; + /* * We have to deal with one special case scenario - the gallery. * The gallery should never be overrided, just added to @@ -230,7 +241,7 @@ public void mergeCheckout(RCheckout checkout) { Collections.sort(team.getTabs()); } } - + if(shouldOverrideLastEdited) team.setLastEdit(checkout.getTeam().getLastEdit()); } // The team was not found locally, create a new one else { @@ -281,8 +292,8 @@ public ArrayList generateCheckoutsFromEvent(RTeam[] teams, long time) newCheckout.setID(id); newCheckout.setStatus(HandoffStatus.AVAILABLE); - if(mode == MODES.BLUETOOTH && newCheckout.getTeam().getLastEdit() >= time) checkouts.add(newCheckout); - else checkouts.add(newCheckout); + if(mode == MODES.BLUETOOTH && newCheckout.getTeam().getLastEdit() > time) checkouts.add(newCheckout); + else if(mode != MODES.BLUETOOTH) checkouts.add(newCheckout); id++; } // Package matches checkouts @@ -300,8 +311,8 @@ public ArrayList generateCheckoutsFromEvent(RTeam[] teams, long time) check.setID(id); check.setStatus(HandoffStatus.AVAILABLE); - if(mode == MODES.BLUETOOTH && check.getTeam().getLastEdit() >= time) checkouts.add(check); - else checkouts.add(check); + if(mode == MODES.BLUETOOTH && check.getTeam().getLastEdit() > time) checkouts.add(check); + else if(mode != MODES.BLUETOOTH) checkouts.add(check); id++; } @@ -343,5 +354,4 @@ public CloudCheckout[] convertStringSerialToCloudCheckouts(String[] serial) { for(int i = 0; i < serial.length; i++) cloudCheckouts[i] = new CloudCheckout(-1, serial[i]); return cloudCheckouts; } - } diff --git a/app/src/main/java/com/cpjd/roblu/sync/bluetooth/BTServer.java b/app/src/main/java/com/cpjd/roblu/sync/bluetooth/BTServer.java index 7eeab33..6afe761 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/bluetooth/BTServer.java +++ b/app/src/main/java/com/cpjd/roblu/sync/bluetooth/BTServer.java @@ -13,7 +13,6 @@ import org.codehaus.jackson.map.DeserializationConfig; import org.codehaus.jackson.map.ObjectMapper; -import org.json.simple.JSONArray; import org.json.simple.parser.JSONParser; import java.util.ArrayList; @@ -76,6 +75,7 @@ public void run() { for(REvent event : events) { if(event.isBluetoothEnabled()) { this.event = event; + break; } } @@ -108,7 +108,7 @@ public void messageReceived(String header, String message) { IO io = new IO(bluetooth.getActivity()); if(header.equals("isActive")) { - bluetooth.send("ACTIVE", String.valueOf(event == null)); + bluetooth.send("ACTIVE", String.valueOf(event != null)); } if(event == null) { return; @@ -119,17 +119,18 @@ public void messageReceived(String header, String message) { // Process scouting data try { JSONParser parser = new JSONParser(); - JSONArray array = (JSONArray) parser.parse(message); + org.json.simple.JSONArray array = (org.json.simple.JSONArray)parser.parse(message); String[] received = new String[array.size()]; for(int i = 0; i < array.size(); i++) received[i] = array.get(i).toString(); syncHelper.unpackCheckouts(syncHelper.convertStringSerialToCloudCheckouts(received), null); - - + Log.d("RBS", "Received "+array.size()+" checkouts from Roblu Scouter."); } catch(Exception e) { - Log.d("RBS", "Failed to process checkouts received over Bluetooth."); + Log.d("RBS", "Failed to process checkouts received over Bluetooth: "+e.getMessage()); } break; case "requestForm": + Log.d("RBS", "Roblu Scouter requested form, responding with it."); + try { bluetooth.send("FORM", mapper.writeValueAsString(io.loadForm(event.getID()))); } catch(Exception e) { @@ -137,6 +138,8 @@ public void messageReceived(String header, String message) { } break; case "requestUI": + Log.d("RBS", "Roblu Scouter requested ui, responding with it."); + try { bluetooth.send("UI", mapper.writeValueAsString(io.loadSettings().getRui())); } catch(Exception e) { @@ -144,24 +147,32 @@ public void messageReceived(String header, String message) { } break; case "requestCheckouts": + Log.d("RBS", "Roblu Scouter requested checkouts, responding with them."); + // Get the timestamp long time = Long.parseLong(message.split(":")[1]); ArrayList checkouts = syncHelper.generateCheckoutsFromEvent(io.loadTeams(event.getID()), time); try { - bluetooth.send("CHECKOUTS", mapper.writeValueAsString(syncHelper.packCheckouts(checkouts))); + bluetooth.send("CHECKOUTS", syncHelper.packCheckouts(checkouts)); } catch(Exception e) { Log.d("RBS", "Failed to map checkouts to Bluetooth output stream."); } break; case "requestNumber": + Log.d("RBS", "Roblu Scouter requested team number, responding with it."); + bluetooth.send("NUMBER", String.valueOf(io.loadSettings().getTeamNumber())); break; case "requestEventName": + Log.d("RBS", "Roblu Scouter requested event name, responding with it."); + bluetooth.send("EVENT_NAME", event.getName()); break; case "DONE": + Log.d("RBS", "Roblu Scouter requested DONE. Confirming."); + bluetooth.send("DONE", "noParams"); pd.dismiss(); bluetooth.disconnect(); diff --git a/app/src/main/java/com/cpjd/roblu/sync/cloud/EventDepacker.java b/app/src/main/java/com/cpjd/roblu/sync/cloud/EventDepacker.java index 9dd0706..90d489b 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/cloud/EventDepacker.java +++ b/app/src/main/java/com/cpjd/roblu/sync/cloud/EventDepacker.java @@ -154,6 +154,7 @@ public void run() { found = true; break; } + t.setLastEdit(checkout.getTime()); } // If not found, create a new team @@ -210,6 +211,7 @@ public void run() { cloudSettings.getCheckoutSyncIDs().put(checkout.getID(), 0L); } + io.saveCloudSettings(cloudSettings); if(listener != null) { listener.success(event); diff --git a/app/src/main/java/com/cpjd/roblu/sync/cloud/InitPacker.java b/app/src/main/java/com/cpjd/roblu/sync/cloud/InitPacker.java index 4f2e252..5f17176 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/cloud/InitPacker.java +++ b/app/src/main/java/com/cpjd/roblu/sync/cloud/InitPacker.java @@ -9,12 +9,13 @@ import com.cpjd.requests.CloudCheckoutRequest; import com.cpjd.roblu.io.IO; import com.cpjd.roblu.models.RCheckout; -import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RSettings; +import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.RTab; import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.sync.SyncHelper; @@ -107,6 +108,21 @@ protected Boolean doInBackground(Void... params) { SyncHelper syncHelper = new SyncHelper(io, event, SyncHelper.MODES.NETWORK); ArrayList checkouts = syncHelper.generateCheckoutsFromEvent(teams, -1); + // Remove field data + try { + for(RCheckout checkout : checkouts) { + for(RTab tab : checkout.getTeam().getTabs()) { + for(RMetric metric : tab.getMetrics()) { + if(metric instanceof RFieldData) { + ((RFieldData) metric).setData(null); + } + } + } + } + } catch(Exception e) { + // Doesn't matter + } + /* * Convert into JSON and upload */ @@ -118,6 +134,7 @@ protected Boolean doInBackground(Void... params) { String serializedUI = mapper.writeValueAsString(settings.getRui()); String eventName = event.getName(); if(eventName == null) eventName = ""; + if(event.getKey() == null) event.setKey(""); CloudCheckoutRequest ccr = new CloudCheckoutRequest(r, settings.getCode()); Log.d("RBS", "Initializing init packer upload..."); boolean success = ccr.init(settings.getTeamNumber(), eventName, serializedForm, serializedUI, serializedCheckouts, event.getKey()); diff --git a/app/src/main/java/com/cpjd/roblu/sync/cloud/Service.java b/app/src/main/java/com/cpjd/roblu/sync/cloud/Service.java index 6455617..d673d8f 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/cloud/Service.java +++ b/app/src/main/java/com/cpjd/roblu/sync/cloud/Service.java @@ -37,6 +37,7 @@ * @since 3.6.1 * @author Will Davies */ + public class Service extends android.app.Service { @Nullable @@ -241,10 +242,11 @@ public void loop() { for(RCheckout checkout : checkouts) { io.deletePendingCheckout(checkout.getID()); } + Notify.notifyNoAction(getApplicationContext(), "Uploaded new checkouts", "Uploaded "+checkouts.size()+" new checkout(s)."); } Log.d("RBS-Service", "Uploaded "+checkouts.size()+" checkouts."); } catch(Exception e) { - Log.d("RBS-Service", "An error occurred while attempting to push /pending/ checkouts."); + Log.d("RBS-Service", "An error occurred while attempting to push /pending/ checkouts: "+e.getMessage()); } io.saveCloudSettings(cloudSettings); Log.d("RBS-Service", "Sleeping Roblu background service for 10 seconds..."); diff --git a/app/src/main/java/com/cpjd/roblu/utils/CheckoutEncoder.java b/app/src/main/java/com/cpjd/roblu/sync/qr/CheckoutEncoder.java similarity index 97% rename from app/src/main/java/com/cpjd/roblu/utils/CheckoutEncoder.java rename to app/src/main/java/com/cpjd/roblu/sync/qr/CheckoutEncoder.java index 52e6b75..9b3f062 100644 --- a/app/src/main/java/com/cpjd/roblu/utils/CheckoutEncoder.java +++ b/app/src/main/java/com/cpjd/roblu/sync/qr/CheckoutEncoder.java @@ -1,4 +1,4 @@ -package com.cpjd.roblu.utils; +package com.cpjd.roblu.sync.qr; import android.util.Log; @@ -192,8 +192,8 @@ public RCheckout decodeCheckout(String string) { ((RChooser) metric).setSelectedIndex(Integer.parseInt(mTokens[4])); String[] values = new String[mTokens.length - 6]; // the amount of values, with the header info removed - for(int l = 5; l < mTokens.length; l++) { - if(!mTokens[l].equals("")) values[l - 5] = mTokens[l]; + for(int l = 5; l < mTokens.length - 1; l++) { + if(mTokens[l] != null && !mTokens[l].equals("")) values[l - 5] = mTokens[l]; } ((RChooser) metric).setValues(values); break; @@ -220,7 +220,7 @@ public RCheckout decodeCheckout(String string) { break; case "T": // textfield metric = new RTextfield(); - metric.setTitle(mTokens[4]); + ((RTextfield)metric).setText((mTokens[4])); break; } if(metric != null) { diff --git a/app/src/main/java/com/cpjd/roblu/sync/qr/QrReader.java b/app/src/main/java/com/cpjd/roblu/sync/qr/QrReader.java index 5ea20e1..38f5ca6 100644 --- a/app/src/main/java/com/cpjd/roblu/sync/qr/QrReader.java +++ b/app/src/main/java/com/cpjd/roblu/sync/qr/QrReader.java @@ -11,7 +11,6 @@ import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.notifications.Notify; import com.cpjd.roblu.sync.SyncHelper; -import com.cpjd.roblu.utils.CheckoutEncoder; import com.dlazaro66.qrcodereaderview.QRCodeReaderView; /** @@ -83,7 +82,7 @@ public void run() { new SyncHelper(getApplicationContext(), event, SyncHelper.MODES.QR).mergeCheckout(checkout); - new IO(getApplicationContext()).savePendingObject(checkout); + new IO(getApplicationContext()).savePendingCheckout(checkout); Notify.notifyMerged(getApplicationContext(), event.getID(), checkout); diff --git a/app/src/main/java/com/cpjd/roblu/tba/ManualScheduleImporter.java b/app/src/main/java/com/cpjd/roblu/tba/ManualScheduleImporter.java new file mode 100644 index 0000000..5e57fdf --- /dev/null +++ b/app/src/main/java/com/cpjd/roblu/tba/ManualScheduleImporter.java @@ -0,0 +1,161 @@ +package com.cpjd.roblu.tba; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.cpjd.roblu.io.IO; +import com.cpjd.roblu.models.RForm; +import com.cpjd.roblu.models.RTab; +import com.cpjd.roblu.models.RTeam; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Manually import a schedule using the schedule import format from Roblu + */ +public class ManualScheduleImporter extends Thread { + + /** + * Used for accessing file referencing and IO + */ + private IO io; + + /** + * The file to load a manual schedule from, should be a text file separated by commas (.CSV) + */ + private Uri uri; + + /** + * The eventID of the event to merge + */ + private int eventID; + + private Context context; + + /** + * This listener will receive notifications about the status of the schedule importing + */ + public ManualScheduleImporterListener listener; + + public interface ManualScheduleImporterListener { + void error(String message); + void success(); + } + + /** + * Attempts to merge the file into an active event + * @param uri The file to read in with custom match schedule information + * @param eventID The event ID to merge teams and matches in + */ + public ManualScheduleImporter(Context context, Uri uri, int eventID, ManualScheduleImporterListener listener) { + this.eventID = eventID; + this.io = new IO(context); + this.context = context; + this.uri = uri; + this.listener = listener; + } + + @Override + public void run() { + RForm form = io.loadForm(eventID); + RTeam[] teams = io.loadTeams(eventID); + + // Load the schedule + ArrayList lines = new ArrayList<>(); + try { + InputStream fis = context.getContentResolver().openInputStream(uri); + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while((line = br.readLine()) != null) { + Log.d("RBS", "Line: "+line); + lines.add(line); + } + } catch(FileNotFoundException e) { + e.printStackTrace(); + listener.error("File was not found on the system."); + return; + } catch(IOException e) { + e.printStackTrace(); + listener.error("File contains syntax errors. Please double check it for accurate syntax."); + return; + } + + if(lines.size() == 0) { + listener.error("File contains no readable data. Please double check syntax."); + return; + } + + /* + * Process the lines, they'll be in a format of + * teamName,teamNumber,Q1R,Q1M1B,S2M3R,F4B, etc. + */ + + for(int i = 0; i < lines.size(); i++) { + try { + String[] tokens = lines.get(i).split(","); + RTeam team = new RTeam(tokens[0], Integer.parseInt(tokens[1]), io.getNewTeamID(eventID)); + /* + * Only add the team if it hasn't been found already + */ + if(teams != null) { + for(RTeam local : teams) { + // Compare name and number, since IDs will be different + if(local.getName().equalsIgnoreCase(team.getName()) && local.getNumber() == team.getNumber()) { + team = local; + break; + } + } + } + + // Verify the team against the form + team.verify(form); + + // The team has been added (or found locally), start processing matches + for(int j = 2; j < tokens.length; j++) { // use j = 2 to ignore name and number + String name = expandMatchName(tokens[j]); + boolean isRedAlliance = name.contains("R"); + name = name.replaceAll("B", "").replaceAll("R", ""); + + RTab tab = new RTab(team.getNumber(), name, form.getMatch(), isRedAlliance, false, 0); + + // Search for it + if(team.getTabs() != null) { + boolean found = false; + for(RTab local : team.getTabs()) { + if(local.getTitle().equalsIgnoreCase(tab.getTitle())) { + tab = local; + found = true; + break; + } + } + + if(!found) { + team.addTab(tab); + Collections.sort(team.getTabs()); + } + } + } + + io.saveTeam(eventID, team); + } catch(Exception e ) { + listener.error("A syntax error occurred one line #"+(i + 1)+". Please double check syntax."); + return; + } + } + listener.success(); + } + + // Expands the match code, NOTE: this doesn't remove the R or B tag + private String expandMatchName(String name) { + return name.replaceAll("QU", "Quarters ").replaceAll("Q", "Quals ") + .replaceAll("S", "Semis ").replaceAll("M", " Match "); + } + +} diff --git a/app/src/main/java/com/cpjd/roblu/tba/SyncTBAEvent.java b/app/src/main/java/com/cpjd/roblu/tba/SyncTBAEvent.java new file mode 100644 index 0000000..e9184f7 --- /dev/null +++ b/app/src/main/java/com/cpjd/roblu/tba/SyncTBAEvent.java @@ -0,0 +1,183 @@ +package com.cpjd.roblu.tba; + +import android.util.Log; + +import com.cpjd.models.Event; +import com.cpjd.roblu.io.IO; +import com.cpjd.roblu.models.RForm; +import com.cpjd.roblu.models.RTab; +import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RBoolean; +import com.cpjd.roblu.models.metrics.RCounter; +import com.cpjd.roblu.models.metrics.RFieldData; +import com.cpjd.roblu.models.metrics.RMetric; +import com.cpjd.roblu.models.metrics.RTextfield; +import com.cpjd.roblu.utils.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; + + +/** + * Adds any newly available information on TheBlueAlliance.com to the target event + * + * @since 4.4.0 + * @author Will Davies + */ +public class SyncTBAEvent extends Thread { + + private Event event; + private RForm form; + private int eventID; + private IO io; + + private SyncTBAEventListener listener; + + public interface SyncTBAEventListener { + void done(); + } + + public SyncTBAEvent(Event event, int eventID, IO io, SyncTBAEventListener listener) { + this.event = event; + this.eventID = eventID; + this.io = io; + this.listener = listener; + } + + @Override + public void run() { + // Load all the teams locally + RTeam[] teams = io.loadTeams(eventID); + form = io.loadForm(eventID); + + /* + * Start processing! + */ + for(int i = 0; i < event.teams.length; i++) { + // Check if the team already exists + boolean found = false; + if(teams != null) { + for(RTeam team : teams) { + if(team.getName().equals(event.teams[i].nickname) && team.getNumber() == event.teams[i].team_number) { + syncTeam(team); + found = true; + break; + } + } + } + if(!found) { + RTeam newTeam = new RTeam(event.teams[i].nickname, (int) event.teams[i].team_number, io.getNewTeamID(eventID)); + syncTeam(newTeam); + } + } + + listener.done(); + } + + private void syncTeam(RTeam team) { + team.verify(form); + + for(int i = 0; i < event.matches.length; i++) { + if(event.matches[i].doesMatchContainTeam(team.getNumber()) == -1) continue; + + RTab newTab = matchModelToTab(i, team.getNumber()); + + if(newTab == null) continue; + // Search for the match within the team + boolean found = false; + for(RTab t : team.getTabs()) { + if(t.getTitle().equalsIgnoreCase(newTab.getTitle())) { + newTab = t; + found = true; + break; + } + } + + // Add the new match + if(!found) { + team.addTab(newTab); + Collections.sort(team.getTabs()); + } + + /* + * Process field data + */ + if(team.getTabs() != null) { + insertFieldData(i, newTab); + } + } + + // Save the team + io.saveTeam(eventID, team); + } + + private void insertFieldData(int index, RTab tab) { + // Check for FieldData metrics + if(tab.getMetrics() != null) { + for(RMetric metric : tab.getMetrics()) { + if(metric instanceof RFieldData) { + if(((RFieldData) metric).getData() == null) ((RFieldData) metric).setData(new LinkedHashMap>()); + + for(int i = 0; i < event.matches[index].scorableItems.length; i++) { + + Log.d("RBS", "Metric name: "+event.matches[index].scorableItems[i]+", "+"Red value: "+event.matches[index].redValues[i]+", Blue value: "+event.matches[index].blueValues[i]); + + ArrayList metrics = new ArrayList<>(); + try { + metrics.add(new RCounter(0, "", 0, Integer.parseInt(event.matches[index].redValues[i]))); + } catch(Exception e) { + try { + metrics.add(new RBoolean(0, "", Boolean.parseBoolean(event.matches[index].redValues[i]))); + } catch(Exception e2) { + metrics.add(new RTextfield(0, "", (event.matches[index].redValues[i]))); + } + + } + try { + metrics.add(new RCounter(0, "", 0, Integer.parseInt(event.matches[index].blueValues[i]))); + } catch(Exception e) { + try { + metrics.add(new RBoolean(0, "", Boolean.parseBoolean(event.matches[index].blueValues[i]))); + } catch(Exception e2) { + metrics.add(new RTextfield(0, "", (event.matches[index].blueValues[i]))); + } + } + + if(event.matches[index].scorableItems[i] != null && metrics.size() > 0) ((RFieldData) metric).getData().put(event.matches[index].scorableItems[i], metrics); + } + } + } + } + } + + private RTab matchModelToTab(int index, int teamNumber) { + int result = event.matches[index].doesMatchContainTeam(teamNumber); + if(result > 0) { + String name = "Match"; + // process the correct match name + switch(event.matches[index].comp_level) { + case "qm": + name = "Quals " + event.matches[index].match_number; + break; + case "qf": + name = "Quarters " + event.matches[index].set_number + " Match " + event.matches[index].match_number; + break; + case "sf": + name = "Semis " + event.matches[index].set_number + " Match " + event.matches[index].match_number; + break; + case "f": + name = "Finals " + event.matches[index].match_number; + } + boolean isRed = result == com.cpjd.main.Constants.CONTAINS_TEAM_RED; + // add the match to the team, make sure to multiple the Event model's matches times by 1000 (seconds to milliseconds, Roblu works with milliseconds!) + RTab tab = new RTab(teamNumber, name, Utils.duplicateRMetricArray(form.getMatch()), isRed, event.matches[index].isOnWinningAlliance(teamNumber), event.matches[index].time * 1000); + // set the match position, if possible + tab.setAlliancePosition(event.matches[index].getTeamPosition(teamNumber)); + return tab; + } + return null; + } + + +} diff --git a/app/src/main/java/com/cpjd/roblu/tba/UnpackTBAEvent.java b/app/src/main/java/com/cpjd/roblu/tba/UnpackTBAEvent.java index c40a9a1..f98a3d8 100644 --- a/app/src/main/java/com/cpjd/roblu/tba/UnpackTBAEvent.java +++ b/app/src/main/java/com/cpjd/roblu/tba/UnpackTBAEvent.java @@ -4,23 +4,26 @@ import android.app.ProgressDialog; import android.content.Intent; import android.os.AsyncTask; +import android.util.Log; import com.cpjd.models.Event; import com.cpjd.roblu.io.IO; -import com.cpjd.roblu.models.RCheckout; -import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RTab; import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RBoolean; +import com.cpjd.roblu.models.metrics.RCounter; +import com.cpjd.roblu.models.metrics.RFieldData; +import com.cpjd.roblu.models.metrics.RMetric; +import com.cpjd.roblu.models.metrics.RTextfield; import com.cpjd.roblu.utils.Constants; -import com.cpjd.roblu.utils.HandoffStatus; import com.cpjd.roblu.utils.Utils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Random; +import java.util.LinkedHashMap; import lombok.Setter; @@ -36,24 +39,17 @@ public class UnpackTBAEvent extends AsyncTask { private Event event; private int eventID; - /** - * If true, UnpackTBAEvent will act as if the event already exists and attempt - * to merge only new data - */ - private boolean merge; - private WeakReference activityWeakReference; private WeakReference progressDialogWeakReference; @Setter private boolean randomize; - public UnpackTBAEvent(Event e, int eventID, boolean merge, Activity activity, ProgressDialog d) { + public UnpackTBAEvent(Event e, int eventID, Activity activity, ProgressDialog d) { this.eventID = eventID; this.event = e; this.activityWeakReference = new WeakReference<>(activity); this.progressDialogWeakReference = new WeakReference<>(d); - this.merge = merge; } protected Void doInBackground(Void... params) { @@ -83,10 +79,6 @@ protected Void doInBackground(Void... params) { IO io = new IO(activityWeakReference.get()); RForm form = io.loadForm(eventID); - // For merge mode - RTeam[] localTeams = null; - if(merge) localTeams = io.loadTeams(eventID); - int result; for(RTeam t : teams) { t.verify(form); @@ -113,6 +105,43 @@ protected Void doInBackground(Void... params) { RTab tab = new RTab(t.getNumber(), name, Utils.duplicateRMetricArray(form.getMatch()), isRed, event.matches[j].isOnWinningAlliance(t.getNumber()), event.matches[j].time * 1000); // set the match position, if possible tab.setAlliancePosition(event.matches[j].getTeamPosition(t.getNumber())); + + // Check for FieldData metrics + if(tab.getMetrics() != null) { + for(RMetric metric : tab.getMetrics()) { + if(metric instanceof RFieldData) { + if(((RFieldData) metric).getData() == null) ((RFieldData) metric).setData(new LinkedHashMap>()); + + for(int i = 0; i < event.matches[j].scorableItems.length; i++) { + + Log.d("RBS", "Metric name: "+event.matches[j].scorableItems[i]+", "+"Red value: "+event.matches[j].redValues[i]+", Blue value: "+event.matches[j].blueValues[i]); + + ArrayList metrics = new ArrayList<>(); + try { + metrics.add(new RCounter(0, "", 0, Integer.parseInt(event.matches[j].redValues[i]))); + } catch(Exception e) { + try { + metrics.add(new RBoolean(0, "", Boolean.parseBoolean(event.matches[j].redValues[i]))); + } catch(Exception e2) { + metrics.add(new RTextfield(0, "", (event.matches[j].redValues[i]))); + } + + } + try { + metrics.add(new RCounter(0, "", 0, Integer.parseInt(event.matches[j].blueValues[i]))); + } catch(Exception e) { + try { + metrics.add(new RBoolean(0, "", Boolean.parseBoolean(event.matches[j].blueValues[i]))); + } catch(Exception e2) { + metrics.add(new RTextfield(0, "", (event.matches[j].blueValues[i]))); + } + } + + if(event.matches[j].scorableItems[i] != null && metrics.size() > 0) ((RFieldData) metric).getData().put(event.matches[j].scorableItems[i], metrics); + } + } + } + } t.addTab(tab); } } @@ -120,68 +149,12 @@ protected Void doInBackground(Void... params) { /* * This is where the merge decision comes into play */ - if(!merge) { - if(randomize) { - t.setLastEdit(System.currentTimeMillis()); - Utils.randomizeTeamMetrics(t.getTabs()); - } - - io.saveTeam(eventID, t); - } else { - REvent localEvent = io.loadEvent(eventID); - - /* - * User wants to merge with an event, we need to do a team merge. - * This involves two things: - * 1) If team doesn't exist locally, just write it new (check for existence with name + number equivalence) - * 2) If a team does exist, add any matches from the team model here that aren't there - */ - if(localTeams != null && localTeams.length > 0) { - boolean found = false; - RTeam localRef = null; - for(RTeam team : localTeams) { - if(team.getName().equals(t.getName()) && team.getNumber() == t.getNumber()) { - found = true; - localRef = team; - } - } - // Team wasn't found locally, so ignore it - if(!found) { - t.setID(io.getNewTeamID(eventID)); - io.saveTeam(eventID, t); - } else { // team was found locally, so do a match merge (only add matches if they're new!) - for(RTab tab : t.getTabs()) { - if(!doesExist(localRef, tab.getTitle())) { - localRef.addTab(tab); - // If these event is cloud synced, a new checkout needs to be packaged - if(localEvent.isCloudEnabled()) { - RTeam newTeam = new RTeam(localRef.getName(), localRef.getNumber(), localRef.getID()); - newTeam.addTab(tab); - RCheckout checkout = new RCheckout(newTeam); - /* - * It would require a lot more code to check all devices and be sure that a new ID is - * valid, so generate a random one. The chances of an error occurring are so low, this is acceptable (somewhat) - */ - checkout.setID(new Random().nextInt(Integer.MAX_VALUE - 50_000) + 20_000); - checkout.setStatus(HandoffStatus.AVAILABLE); - io.savePendingObject(checkout); - } - - } - - // Update the match wins - for(RTab tab1 : localRef.getTabs()) { - if(tab1.getTitle().equalsIgnoreCase(tab.getTitle())) tab1.setWon(tab.isWon()); - } - } - Collections.sort(localRef.getTabs()); - io.saveTeam(eventID, localRef); - } - } else { - io.saveTeam(eventID, t); - } + if(randomize) { + t.setLastEdit(System.currentTimeMillis()); + Utils.randomizeTeamMetrics(t.getTabs()); } + io.saveTeam(eventID, t); } return null; } diff --git a/app/src/main/java/com/cpjd/roblu/ui/events/EventCreateMethodPicker.java b/app/src/main/java/com/cpjd/roblu/ui/events/EventCreateMethodPicker.java index 0d9bb3e..82d3719 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/events/EventCreateMethodPicker.java +++ b/app/src/main/java/com/cpjd/roblu/ui/events/EventCreateMethodPicker.java @@ -328,6 +328,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { Utils.showSnackbar(findViewById(R.id.activity_create_event_picker), getApplicationContext(), "Invalid backup file", true, rui.getPrimaryColor()); } } + /* * The user created an event manually with EventEditor, we actually don't need to do anything but auto-finish our class * with a result code letting the TeamsView class now to refresh the event list diff --git a/app/src/main/java/com/cpjd/roblu/ui/events/EventDrawerManager.java b/app/src/main/java/com/cpjd/roblu/ui/events/EventDrawerManager.java index abf496d..a8c7909 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/events/EventDrawerManager.java +++ b/app/src/main/java/com/cpjd/roblu/ui/events/EventDrawerManager.java @@ -162,7 +162,7 @@ public EventDrawerManager(final Activity activity, Toolbar toolbar, EventSelectL public void onCheckedChanged(final IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked) { if(drawerItem.getIdentifier() == Constants.BLUETOOTH_SERVER) { if(((SwitchDrawerItem)drawerItem).isChecked()) { - ProgressDialog dialog = ProgressDialog.show(bluetooth.getActivity(), "Listening for incoming connections...", "Waiting for a device to connect...", false); + ProgressDialog dialog = ProgressDialog.show(bluetooth.getActivity(), "Bluetooth Server", "Bluetooth server is online", false); dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { diff --git a/app/src/main/java/com/cpjd/roblu/ui/events/EventEditor.java b/app/src/main/java/com/cpjd/roblu/ui/events/EventEditor.java index e834017..89024f8 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/events/EventEditor.java +++ b/app/src/main/java/com/cpjd/roblu/ui/events/EventEditor.java @@ -272,7 +272,7 @@ private void createEvent(RForm form) { d.setCancelable(false); event.setKey(((Event)getIntent().getSerializableExtra("tbaEvent")).key); io.saveEvent(event); - UnpackTBAEvent unpackTBAEvent = new UnpackTBAEvent((Event)getIntent().getSerializableExtra("tbaEvent"), event.getID(), false, this, d); + UnpackTBAEvent unpackTBAEvent = new UnpackTBAEvent((Event)getIntent().getSerializableExtra("tbaEvent"), event.getID(), this, d); if(((Switch)findViewById(R.id.switch1)).isChecked()) unpackTBAEvent.setRandomize(true); unpackTBAEvent.execute(); } diff --git a/app/src/main/java/com/cpjd/roblu/ui/events/EventSettings.java b/app/src/main/java/com/cpjd/roblu/ui/events/EventSettings.java index 7e1b47c..819d9a6 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/events/EventSettings.java +++ b/app/src/main/java/com/cpjd/roblu/ui/events/EventSettings.java @@ -12,7 +12,10 @@ import android.Manifest; import android.app.ProgressDialog; +import android.content.Context; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.StrictMode; @@ -40,8 +43,9 @@ import com.cpjd.roblu.sync.cloud.InitPacker; import com.cpjd.roblu.sync.qr.QrReader; import com.cpjd.roblu.tba.ImportEvent; +import com.cpjd.roblu.tba.ManualScheduleImporter; +import com.cpjd.roblu.tba.SyncTBAEvent; import com.cpjd.roblu.tba.TBALoadEventsTask; -import com.cpjd.roblu.tba.UnpackTBAEvent; import com.cpjd.roblu.ui.UIHandler; import com.cpjd.roblu.ui.dialogs.FastDialogBuilder; import com.cpjd.roblu.ui.forms.FormViewer; @@ -142,6 +146,8 @@ public static class SettingsFragment extends PreferenceFragment implements Prefe */ private ProgressDialog tbaSyncDialog; + private int fileChooserMode; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -164,6 +170,7 @@ public void onCreate(Bundle savedInstanceState) { findPreference("delete_teams").setOnPreferenceClickListener(this); findPreference("delete_event").setOnPreferenceClickListener(this); findPreference("tba_sync").setOnPreferenceClickListener(this); + findPreference("import_schedule").setOnPreferenceClickListener(this); findPreference("qr").setOnPreferenceClickListener(this); RUICheckPreference bt = (RUICheckPreference) findPreference("bt_sync"); bt.setChecked(event.isBluetoothEnabled()); @@ -210,6 +217,21 @@ public boolean onPreferenceClick(final Preference preference) { qrScanIntent.putExtra("event", event); startActivityForResult(qrScanIntent, Constants.QR_REQUEST); } + // Manual schedule importer + else if(preference.getKey().equals("import_schedule")) { + fileChooserMode = 1; + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + try { + startActivityForResult( + Intent.createChooser(intent, "Select a schedule file"), + Constants.FILE_CHOOSER); + } catch (android.content.ActivityNotFoundException ex) { + Utils.showSnackbar(getActivity().findViewById(R.id.activity_create_event_picker), getActivity(), "No file manager found", true, 0); + } + } /* * User clicked the "Edit event info" preference. * Keep in mind, we'll need to listen to the EventEditor for changes @@ -225,6 +247,11 @@ else if(preference.getKey().equals("edit_event")) { * User clicked "Sync with TBA" option */ else if(preference.getKey().equalsIgnoreCase("tba_sync")) { + if(event.getKey() == null || event.getKey().equalsIgnoreCase("")) { + Utils.showSnackbar(getActivity().findViewById(R.id.event_settings), getActivity(), "No TBA key found. Set it in this event's settings.", true, 0); + return true; + } + // Download the entire event tbaSyncDialog = ProgressDialog.show(getActivity(), "Syncing event with TheBlueAlliance...", "This may take several seconds...", false); tbaSyncDialog.setCancelable(false); @@ -237,7 +264,12 @@ public void errorOccurred(String errMsg) { @Override public void eventDownloaded(Event e) { // Start the merge! - new UnpackTBAEvent(e, event.getID(), true, getActivity(), tbaSyncDialog).execute(); + new SyncTBAEvent(e, event.getID(), new IO(getActivity()), new SyncTBAEvent.SyncTBAEventListener() { + @Override + public void done() { + tbaSyncDialog.dismiss(); + } + }).start(); } @Override @@ -283,6 +315,8 @@ public void neutral() { * User clicked on "Backup event" */ else if(preference.getKey().equals("backup")) { + fileChooserMode = 2; + if(EasyPermissions.hasPermissions(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Star the task new BackupEventTask(new IO(getActivity()), new BackupEventTask.BackupEventTaskListener() { @@ -487,7 +521,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { * Received after the user selected a backup file location and data needs to be * copied to it */ - if(requestCode == Constants.FILE_CHOOSER) { + if(requestCode == Constants.FILE_CHOOSER && fileChooserMode == 2) { try { /* * This will copy the internal backup file to the external location the user selected @@ -503,6 +537,36 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } catch(Exception e) { Utils.showSnackbar(getActivity().findViewById(R.id.event_settings), getActivity(), "Error occurred while creating backup", true, 0); } + + fileChooserMode = 0; + } + else if(requestCode == Constants.FILE_CHOOSER && fileChooserMode == 1) { + // this means the user didn't select a file, no point in returning an error message + if(data == null) return; + + new ManualScheduleImporter(getActivity(), data.getData(), event.getID(), new ManualScheduleImporter.ManualScheduleImporterListener() { + @Override + public void error(final String message) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getActivity(), "An error occurred: "+message, Toast.LENGTH_LONG).show(); + } + }); + } + + @Override + public void success() { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getActivity(), "Successfully imported manual match schedule.", Toast.LENGTH_LONG).show(); + } + }); + } + }).start(); + + fileChooserMode = 0; } /* * Called when event info was edited @@ -533,7 +597,7 @@ public static class BackupEventTask extends AsyncTask { private WeakReference ioWeakReference; - interface BackupEventTaskListener { + public interface BackupEventTaskListener { void eventBackupComplete(File backupFile); } @@ -542,7 +606,7 @@ interface BackupEventTaskListener { */ private BackupEventTaskListener listener; - BackupEventTask(IO io, BackupEventTaskListener listener) { + public BackupEventTask(IO io, BackupEventTaskListener listener) { this.listener = listener; this.ioWeakReference = new WeakReference<>(io); } @@ -550,13 +614,35 @@ interface BackupEventTaskListener { protected File doInBackground(Void... params) { RTeam[] teams = ioWeakReference.get().loadTeams(event.getID()); RForm form = ioWeakReference.get().loadForm(event.getID()); - return ioWeakReference.get().saveBackup(new RBackup(event, teams, form)); + return ioWeakReference.get().saveBackup(new RBackup(event, teams, form), "event.roblubackup"); } protected void onPostExecute(File file) { listener.eventBackupComplete(file); } } + + private static String getPath(Context context, Uri uri) { + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = { "_data" }; + Cursor cursor; + + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + int column_index = cursor.getColumnIndexOrThrow("_data"); + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + // Eat it + } + } + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } } } diff --git a/app/src/main/java/com/cpjd/roblu/ui/forms/FormViewer.java b/app/src/main/java/com/cpjd/roblu/ui/forms/FormViewer.java index cfe92f2..ca6de5f 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/forms/FormViewer.java +++ b/app/src/main/java/com/cpjd/roblu/ui/forms/FormViewer.java @@ -14,12 +14,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import com.cpjd.roblu.R; import com.cpjd.roblu.io.IO; import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RUI; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.models.metrics.RTextfield; import com.cpjd.roblu.ui.UIHandler; @@ -308,6 +310,12 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { changesMade = true; Bundle b = data.getExtras(); RMetric metric = (RMetric) b.getSerializable("metric"); + + if(metric instanceof RFieldData && currentTab == 0) { + Toast.makeText(getApplicationContext(), "You can't add the field data metric to the pit tab.", Toast.LENGTH_LONG).show(); + return; + } + metricsAdapter.addMetric(metric); } /* diff --git a/app/src/main/java/com/cpjd/roblu/ui/forms/MetricEditor.java b/app/src/main/java/com/cpjd/roblu/ui/forms/MetricEditor.java index 9f3117b..dd8aef9 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/forms/MetricEditor.java +++ b/app/src/main/java/com/cpjd/roblu/ui/forms/MetricEditor.java @@ -39,6 +39,7 @@ import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RCounter; import com.cpjd.roblu.models.metrics.RDivider; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RFieldDiagram; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; @@ -83,7 +84,7 @@ public class MetricEditor extends AppCompatActivity implements AdapterView.OnIte /** * All the different metric types that the user can select from */ - private final static String[] METRIC_TYPES = {"Boolean", "Counter", "Slider", "Chooser", "Checkbox", "Stopwatch", "Textfield", "Gallery", "Divider", "Field", "Calculation"}; + private final static String[] METRIC_TYPES = {"Boolean", "Counter", "Slider", "Chooser", "Checkbox", "Stopwatch", "Textfield", "Gallery", "Divider", "Field", "Calculation", "Field data"}; /** * The user's color preferences, so the metrics can be synced with the user's preferences */ @@ -433,6 +434,7 @@ private void addMetricPreviewToToolbar() { else if(metric instanceof RDivider) toolbar.addView(rMetricToUI.getDivider((RDivider)metric)); else if(metric instanceof RFieldDiagram) toolbar.addView(rMetricToUI.getFieldDiagram(-1, (RFieldDiagram)metric)); else if(metric instanceof RCalculation) toolbar.addView(rMetricToUI.getCalculationMetric(null, ((RCalculation)metric))); + else if(metric instanceof RFieldData) toolbar.addView(rMetricToUI.getFieldData((RFieldData)metric)); } /** @@ -517,6 +519,8 @@ public void onItemSelected(AdapterView adapterView, View view, int i, long l) metric = new RFieldDiagram(0, R.drawable.field2018, null); } else if(stringOfSelected.equals(METRIC_TYPES[10])) { metric = new RCalculation(0, "Custom calculation"); + } else if(stringOfSelected.equals(METRIC_TYPES[11])) { + metric = new RFieldData(0, "Match data"); } metric.setModified(true); diff --git a/app/src/main/java/com/cpjd/roblu/ui/forms/RMetricToUI.java b/app/src/main/java/com/cpjd/roblu/ui/forms/RMetricToUI.java index a5a789d..1676fd0 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/forms/RMetricToUI.java +++ b/app/src/main/java/com/cpjd/roblu/ui/forms/RMetricToUI.java @@ -42,6 +42,8 @@ import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.Spinner; +import android.widget.TableLayout; +import android.widget.TableRow; import android.widget.TextView; import com.cpjd.roblu.R; @@ -53,6 +55,7 @@ import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RCounter; import com.cpjd.roblu.models.metrics.RDivider; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RFieldDiagram; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; @@ -208,6 +211,8 @@ public void onClick(View v) { observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(bool); @@ -424,6 +429,8 @@ public void afterTextChanged(Editable editable) {} observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(counter); } @@ -482,6 +489,8 @@ public CardView getSlider(final RSlider slider) { observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(slider); @@ -566,6 +575,8 @@ public CardView getChooser(final RChooser chooser) { observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(chooser); } @@ -650,6 +661,8 @@ public CardView getCheckbox(final RCheckbox checkbox) { observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(checkbox); } @@ -844,6 +857,8 @@ public CardView getStopwatch(final RStopwatch stopwatch, final boolean demo) { observed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + + if(!editable) return; layout.removeView(observed); listener.changeMade(stopwatch); } @@ -1148,6 +1163,52 @@ public void onClick(View v) { return getCard(layout); } + public CardView getFieldData(RFieldData fieldData) { + RelativeLayout layout = new RelativeLayout(activity); + + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + + TextView text = new TextView(activity); + text.setText(fieldData.getTitle()); + text.setId(Utils.generateViewId()); + TableLayout tableLayout = new TableLayout(activity); + tableLayout.setId(Utils.generateViewId()); + + if(fieldData.getData() != null) { + for(Object key : fieldData.getData().keySet()) { + + TableRow tableRow = new TableRow(activity); + TextView red = new TextView(activity); + red.setBackgroundColor(Color.RED); + red.setText(fieldData.getData().get(key).get(0).toString()); + + TextView title = new TextView(activity); + title.setPadding(Utils.DPToPX(activity, 15), title.getPaddingTop(), Utils.DPToPX(activity, 15), title.getPaddingBottom()); + title.setText(key.toString()); + + TextView blue = new TextView(activity); + blue.setBackgroundColor(Color.BLUE); + blue.setText(fieldData.getData().get(key).get(1).toString()); + + tableRow.setBackgroundResource(R.drawable.row_border); + + tableRow.addView(red); + tableRow.addView(title); + tableRow.addView(blue); + tableLayout.addView(tableRow); + } + } + + tableLayout.setGravity(Gravity.CENTER_HORIZONTAL); + params.addRule(RelativeLayout.BELOW, text.getId()); + layout.setGravity(Gravity.CENTER_HORIZONTAL); + tableLayout.setLayoutParams(params); + layout.addView(text); + layout.addView(tableLayout); + + return getCard(layout); + } + public CardView getInfoField(final String name, String data, final String website, final int number) { RelativeLayout layout = new RelativeLayout(activity); TextView textView = new TextView(activity); diff --git a/app/src/main/java/com/cpjd/roblu/ui/images/Drawing.java b/app/src/main/java/com/cpjd/roblu/ui/images/Drawing.java index 887cc50..c1a2e5f 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/images/Drawing.java +++ b/app/src/main/java/com/cpjd/roblu/ui/images/Drawing.java @@ -167,7 +167,9 @@ public boolean onMenuItemClick(MenuItem item) { canvas.setDrawer(CanvasView.Drawer.PEN); return true; } else if(item.getItemId() == R.id.eraser) { - canvas.clear(); + DRAWINGS = null; + getIntent().putExtra("fieldDrawings", DRAWINGS); + recreate(); return true; } else if(item.getItemId() == R.id.line) { diff --git a/app/src/main/java/com/cpjd/roblu/ui/images/FullScreenImageGalleryActivity.java b/app/src/main/java/com/cpjd/roblu/ui/images/FullScreenImageGalleryActivity.java index a6ebe83..ba58baa 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/images/FullScreenImageGalleryActivity.java +++ b/app/src/main/java/com/cpjd/roblu/ui/images/FullScreenImageGalleryActivity.java @@ -100,7 +100,7 @@ protected void onDestroy() { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if(resultCode == Constants.IMAGE_EDITED) { - setResult(Constants.IMAGE_EDITED); + setResult(Constants.IMAGE_EDITED, data); finish(); } } @@ -114,7 +114,7 @@ public boolean onOptionsItemSelected(MenuItem item) { /* * User wants to delete an image */ - else if(item.getItemId() == R.id.delete_image) { + else if(item.getItemId() == R.id.delete_image && editable) { Intent result = new Intent(); result.putExtra("position", viewPager.getCurrentItem()); setResult(Constants.IMAGE_DELETED, result); diff --git a/app/src/main/java/com/cpjd/roblu/ui/images/ImageGalleryActivity.java b/app/src/main/java/com/cpjd/roblu/ui/images/ImageGalleryActivity.java index 5de1246..3e93c7e 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/images/ImageGalleryActivity.java +++ b/app/src/main/java/com/cpjd/roblu/ui/images/ImageGalleryActivity.java @@ -239,6 +239,19 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { else if(resultCode == Constants.IMAGE_EDITED) { TeamViewer.team.setLastEdit(System.currentTimeMillis()); + /* + * Update the image in the gallery + */ + for(int i = 0; i < TeamViewer.team.getTabs().get(rTabIndex).getMetrics().size(); i++) { + if(TeamViewer.team.getTabs().get(rTabIndex).getMetrics().get(i).getID() == galleryID) { + if(((RGallery)TeamViewer.team.getTabs().get(rTabIndex).getMetrics().get(i)).getPictureIDs() == null) { + ((RGallery)TeamViewer.team.getTabs().get(rTabIndex).getMetrics().get(i)).setPictureIDs(new ArrayList()); + } + ((RGallery)TeamViewer.team.getTabs().get(rTabIndex).getMetrics().get(i)).getPictureIDs().add(new IO(getApplicationContext()).savePicture(eventID, IMAGES.get(data.getIntExtra("position", 0)))); + break; + } + } + new IO(getApplicationContext()).saveTeam(eventID, TeamViewer.team); imageGalleryAdapter.notifyDataSetChanged(); } diff --git a/app/src/main/java/com/cpjd/roblu/ui/settings/AdvSettings.java b/app/src/main/java/com/cpjd/roblu/ui/settings/AdvSettings.java index 96bcd4f..969b2c2 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/settings/AdvSettings.java +++ b/app/src/main/java/com/cpjd/roblu/ui/settings/AdvSettings.java @@ -10,6 +10,7 @@ package com.cpjd.roblu.ui.settings; +import android.Manifest; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; @@ -33,6 +34,7 @@ import android.support.v7.widget.Toolbar; import android.text.InputFilter; import android.text.InputType; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -45,18 +47,25 @@ import com.cpjd.requests.CloudTeamRequest; import com.cpjd.roblu.R; import com.cpjd.roblu.io.IO; -import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.REvent; +import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RSettings; +import com.cpjd.roblu.models.RSyncSettings; import com.cpjd.roblu.models.RUI; import com.cpjd.roblu.sync.bluetooth.Bluetooth; import com.cpjd.roblu.ui.UIHandler; import com.cpjd.roblu.ui.dialogs.FastDialogBuilder; import com.cpjd.roblu.utils.Constants; import com.cpjd.roblu.utils.Utils; +import com.google.common.io.Files; import com.mikepenz.aboutlibraries.Libs; import com.mikepenz.aboutlibraries.LibsBuilder; +import java.io.File; +import java.io.OutputStream; + +import pub.devrel.easypermissions.EasyPermissions; + /** * * AdvSettings is short for "Advanced Settings", because the last version of settings was absolute garbage. @@ -134,6 +143,8 @@ public static class SettingsFragment extends PreferenceFragment implements Prefe "2.0.0-2.9.9\nRoblu Version 2, we don't talk about that anymore\n\n1.0.0-1.9.9\nRoblu Version 1 is where humans go to die"; + private int masterMode; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -162,6 +173,8 @@ public void onCreate(Bundle savedInstanceState) { findPreference("reddit").setOnPreferenceClickListener(this); findPreference("purge").setOnPreferenceClickListener(this); findPreference("bt_devices").setOnPreferenceClickListener(this); + findPreference("import_master_form").setOnPreferenceClickListener(this); + findPreference("backup_master_form").setOnPreferenceClickListener(this); CheckBoxPreference opted = (CheckBoxPreference) findPreference("opt_in"); opted.setOnPreferenceChangeListener(this); opted.setChecked(new IO(getActivity()).loadCloudSettings().isOptedIn()); @@ -184,6 +197,8 @@ private void toggleJoinTeam(boolean b) { } } + private File backupFile; + // Called when the user taps a preference @Override public boolean onPreferenceClick(Preference preference) { @@ -269,6 +284,49 @@ else if(preference.getKey().equals("customizer")) { // launch the UI customizer, getActivity().startActivityForResult(new Intent(getActivity(), UICustomizer.class), Constants.GENERAL); return true; } + // user wants to import a master form from the file system + else if(preference.getKey().equals("import_master_form")) { + + masterMode = 1; + +/* + * Open a file chooser where the user can select a backup file to use. + * We'll listen to a result in onActivityResult() and import the backup file there + */ + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + try { + startActivityForResult( + Intent.createChooser(intent, "Select a .roblubackup file"), + Constants.FILE_CHOOSER); + } catch (android.content.ActivityNotFoundException ex) { + Utils.showSnackbar(getActivity().findViewById(R.id.activity_create_event_picker), getActivity(), "No file manager found", true, settings.getRui().getPrimaryColor()); + } + } + // user wants to export the master form to the file system + else if(preference.getKey().equals("backup_master_form")) { + masterMode = 2; + + if(EasyPermissions.hasPermissions(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + // Copy the master form to a file on the system + RForm form = new IO(getActivity()).loadSettings().getMaster(); + + IO io = new IO(getActivity()); + io.saveForm(-1, form); + backupFile = new File(getActivity().getFilesDir(), IO.PREFIX+File.separator+"master_form.ser"); + Log.d("RBS", "Backup file: "+backupFile.exists()); + + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.setType("application/*"); + intent.putExtra(Intent.EXTRA_TITLE, backupFile.getName()); + startActivityForResult(intent, Constants.FILE_CHOOSER); + + } else { + Utils.showSnackbar(getActivity().findViewById(R.id.event_settings), getActivity(), "Storage permission is disabled. Please enable it.", true, settings.getRui().getPrimaryColor()); + } + } + return false; } @@ -510,6 +568,49 @@ public void onClick(View v) { } } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + /* + * The user selected a backup file, let's attempt to import it here + */ + if(requestCode == Constants.FILE_CHOOSER && masterMode == 1) { + // this means the user didn't select a file, no point in returning an error message + if(data == null) return; + + try { + IO io = new IO(getActivity()); + RForm form = io.convertFormFile(data.getData()); + settings.setMaster(form); + io.saveSettings(settings); + Toast.makeText(getActivity(), "Successfully imported master form.", Toast.LENGTH_LONG).show(); + } catch(Exception e) { + Log.d("RBS", "Error: "+e.getMessage()); + Toast.makeText(getActivity(), "Invalid master form backup.", Toast.LENGTH_LONG).show(); + } + } + /* + * User exported a file, copy to the backup location + */ + else if(requestCode == Constants.FILE_CHOOSER && masterMode == 2) { + try { + /* + * This will copy the internal backup file to the external location the user selected + * (internal is app data, external is a location the user can see, but still on internal storage for the device) + */ + OutputStream os = getActivity().getContentResolver().openOutputStream(data.getData()); + if(os != null) { + Files.copy(backupFile, os); + os.flush(); + os.close(); + Toast.makeText(getActivity(), "Successfully created master form backup", Toast.LENGTH_LONG).show(); + } else Toast.makeText(getActivity(), "Error occurred while creating master form backup", Toast.LENGTH_LONG).show(); + } catch(Exception e) { + Toast.makeText(getActivity(), "Error occurred while creating master form backup"+e.getMessage(), Toast.LENGTH_LONG).show(); + } + } + + masterMode = 0; + } } // load in the bug report button, and make it match the ui settings diff --git a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Match.java b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Match.java index e4cd3b2..2c70913 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Match.java +++ b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Match.java @@ -24,6 +24,7 @@ import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RCounter; import com.cpjd.roblu.models.metrics.RDivider; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RFieldDiagram; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; @@ -132,6 +133,7 @@ private void loadMetric(RMetric e) { else if(e instanceof RDivider) layout.addView(els.getDivider((RDivider)e)); else if(e instanceof RFieldDiagram) layout.addView(els.getFieldDiagram(position, (RFieldDiagram)e)); else if(e instanceof RCalculation) layout.addView(els.getCalculationMetric(TeamViewer.team.getTabs().get(position).getMetrics(), ((RCalculation)e))); + else if(e instanceof RFieldData && !TeamViewer.team.getTabs().get(position).getTitle().equalsIgnoreCase("PREDICTIONS")) layout.addView(els.getFieldData((RFieldData)e)); else Log.d("RBS", "Couldn't resolve metric with name: "+e.getTitle()); } diff --git a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Overview.java b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Overview.java index 13f4781..fc9c184 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Overview.java +++ b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/Overview.java @@ -22,6 +22,7 @@ import com.cpjd.roblu.models.metrics.RCheckbox; import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RDivider; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RFieldDiagram; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; @@ -130,7 +131,7 @@ else if(metric instanceof RCheckbox) { // Return for incompatible metrics if(team.getTabs().get(1).getMetrics().get(i) instanceof RDivider || team.getTabs().get(1).getMetrics().get(i) instanceof RGallery - || team.getTabs().get(1).getMetrics().get(i) instanceof RTextfield || team.getTabs().get(1).getMetrics().get(i) instanceof RFieldDiagram) continue; + || team.getTabs().get(1).getMetrics().get(i) instanceof RTextfield || team.getTabs().get(1).getMetrics().get(i) instanceof RFieldDiagram || team.getTabs().get(1).getMetrics().get(i) instanceof RFieldData) continue; /* * If the metric was boolean, chooser, or checkbox, all the values need to be diff --git a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/TeamTabAdapter.java b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/TeamTabAdapter.java index 864e2b8..757d63f 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/team/fragments/TeamTabAdapter.java +++ b/app/src/main/java/com/cpjd/roblu/ui/team/fragments/TeamTabAdapter.java @@ -150,11 +150,11 @@ public int createMatch(String name, boolean isRed) { RCheckout checkout = new RCheckout(newTeam); /* * It would require a lot more code to check all devices and be sure that a new ID is - * valid, so generate a random one. The chances of an error occurring are so low, this is acceptable (somewhat) + * valid, so generate a random one. The chances of an error occurring are so low, this is acceptable (somewhat :\) */ checkout.setID(new Random().nextInt(Integer.MAX_VALUE - 50_000) + 20_000); checkout.setStatus(HandoffStatus.AVAILABLE); - new IO(context).savePendingObject(checkout); + new IO(context).savePendingCheckout(checkout); } Bundle bundle = new Bundle(); diff --git a/app/src/main/java/com/cpjd/roblu/ui/teams/LoadTeamsTask.java b/app/src/main/java/com/cpjd/roblu/ui/teams/LoadTeamsTask.java index bc13cf2..9dc27af 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/teams/LoadTeamsTask.java +++ b/app/src/main/java/com/cpjd/roblu/ui/teams/LoadTeamsTask.java @@ -239,6 +239,9 @@ else if(filter == TeamsView.SORT_TYPE.CUSTOM_SORT) { teamMetricProcessor.setInMatchTitle(customSortToken.split(":")[2]); Log.d("RBS", "In match title: "+teamMetricProcessor.getInMatchTitle()); shouldHideZeroRelevance = true; + } else if(methodID == TeamMetricProcessor.PROCESS_METHOD.MATCHES && customSortToken.split(":").length == 3) { + teamMetricProcessor.setInMatchTitle(customSortToken.split(":")[2]); + Log.d("RBS", "In match title: "+teamMetricProcessor.getInMatchTitle()); } /* diff --git a/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsView.java b/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsView.java index 2391d5c..ac4bc36 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsView.java +++ b/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsView.java @@ -34,6 +34,7 @@ import com.cpjd.main.TBA; import com.cpjd.roblu.R; import com.cpjd.roblu.io.IO; +import com.cpjd.roblu.models.RCheckout; import com.cpjd.roblu.models.REvent; import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RSettings; @@ -303,7 +304,7 @@ public boolean onQueryTextChange(String newText) { settings.setUpdateLevel(Constants.VERSION); AlertDialog.Builder builder = new AlertDialog.Builder(TeamsView.this) - .setTitle("Changelist for Version 4.3.0") + .setTitle("Changelist for Version 4.4.0") .setMessage(Constants.UPDATE_MESSAGE) .setPositiveButton("Rock on", new DialogInterface.OnClickListener() { @Override @@ -430,6 +431,17 @@ private void showTeamCreateDialog() { public void onClick(DialogInterface dialog, int which) { if(input2.getText().toString().equals("")) input2.setText("0"); RTeam team = new RTeam(input.getText().toString(), Integer.parseInt(input2.getText().toString()), io.getNewTeamID(eventDrawerManager.getEvent().getID())); + + /* + * Package for cloud + */ + if(eventDrawerManager.getEvent() != null && eventDrawerManager.getEvent().isCloudEnabled()) { + team.verify(io.loadForm(eventDrawerManager.getEvent().getID())); + RCheckout checkout = new RCheckout(team); + checkout.setStatus(0); + io.savePendingCheckout(checkout); + } + io.saveTeam(eventDrawerManager.getEvent().getID(), team); executeLoadTeamsTask(lastFilter, true); } diff --git a/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/MetricSortFragment.java b/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/MetricSortFragment.java index 1329e89..810c877 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/MetricSortFragment.java +++ b/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/MetricSortFragment.java @@ -21,9 +21,12 @@ import com.cpjd.roblu.R; import com.cpjd.roblu.io.IO; +import com.cpjd.roblu.models.RTab; +import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RMetric; -import com.cpjd.roblu.ui.forms.FormRecyclerTouchHelper; import com.cpjd.roblu.ui.forms.FormRecyclerAdapter; +import com.cpjd.roblu.ui.forms.FormRecyclerTouchHelper; import com.cpjd.roblu.utils.Constants; import com.cpjd.roblu.utils.Utils; @@ -100,13 +103,72 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c /** * This method is called when the user taps on a metric + * * @param v the View that the user tapped on (used for inferring the RMetric object) */ @Override public void metricSelected(View v) { - int position = rv.getChildLayoutPosition(v); + final int position = rv.getChildLayoutPosition(v); + + if(metrics.get(position) instanceof RFieldData) { + final Dialog d = new Dialog(getActivity()); + d.setTitle("Select sub metric"); + d.setContentView(R.layout.submetric_import); + final Spinner spinner = d.findViewById(R.id.type); + + // Attempt to load a team to get a list of values + int id = 0; + IO io = new IO(getActivity()); + RTeam team; + do { + team = io.loadTeam(eventID, id); + id++; + } while(team == null && id < 50); + + RFieldData fieldData = null; + try { + mainLoop: + for(RTab tab : team.getTabs()) { + if(tab.getTitle().equalsIgnoreCase("PIT") || tab.getTitle().equalsIgnoreCase("PREDICTIONS")) continue; + for(RMetric metric2 : tab.getMetrics()) { + if(metric2 instanceof RFieldData && metrics.get(position).getID() == metric2.getID()) { + fieldData = (RFieldData) metric2; + break mainLoop; + } + } + } + } catch(Exception e) {//} + } + if(fieldData == null) return; + + final String[] values = Utils.depackFieldData(fieldData); + if(values == null) { + Toast.makeText(getActivity(), "Error occurred while loading metrics.", Toast.LENGTH_LONG).show(); + return; + } + ArrayAdapter adp = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, values); + adp.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adp); + Button button = d.findViewById(R.id.button7); + button.setText(R.string.select); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent result = new Intent(); + result.putExtra("sortToken", TeamMetricProcessor.PROCESS_METHOD.MATCHES + ":" + metrics.get(position).getID() + ":" + values[spinner.getSelectedItemPosition()]); + getActivity().setResult(Constants.CUSTOM_SORT_CONFIRMED, result); + getActivity().finish(); + d.dismiss(); + } + }); + if(d.getWindow() != null) d.getWindow().getAttributes().windowAnimations = new IO(getActivity()).loadSettings().getRui().getAnimation(); + d.show(); + return; + + } + // User selected the "In Match" option, now we have to display a list of all the matches within the event - if(processMethod == TeamMetricProcessor.PROCESS_METHOD.OTHER && metrics.get(position).getID() == TeamMetricProcessor.PROCESS_METHOD.OTHER_METHOD.IN_MATCH) { + else if(processMethod == TeamMetricProcessor.PROCESS_METHOD.OTHER && metrics.get(position).getID() == TeamMetricProcessor.PROCESS_METHOD.OTHER_METHOD.IN_MATCH) { final Dialog d = new Dialog(getActivity()); d.setTitle("Select match"); d.setContentView(R.layout.event_import_dialog); diff --git a/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/TeamMetricProcessor.java b/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/TeamMetricProcessor.java index 0e12c39..2ce2ac1 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/TeamMetricProcessor.java +++ b/app/src/main/java/com/cpjd/roblu/ui/teamsSorting/TeamMetricProcessor.java @@ -6,6 +6,7 @@ import com.cpjd.roblu.models.metrics.RCheckbox; import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RCounter; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RGallery; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.models.metrics.RSlider; @@ -50,7 +51,7 @@ public static class PROCESS_METHOD { /** * Metric (matching inputted ID) should be analyzed from the MATCHES within each team */ - static final int MATCHES = 2; + public static final int MATCHES = 2; public static final int OTHER = 3; @@ -183,6 +184,7 @@ else if(metric instanceof RCounter) { double value = ((RCounter) metric).getValue(); // Overview stats will only consider modified items if(metric.isModified()) { + /* * Progressively calculate the min, max, and average values */ @@ -267,6 +269,11 @@ else if(metric instanceof RChooser) { // add raw data rawData.append(((RChooser)metric).getValues()[((RChooser)metric).getSelectedIndex()]).append(ending(i, team.getTabs())); } + // Field data + else if(metric instanceof RFieldData) { + // Find the sub metric + if(((RFieldData) metric).getData() != null) rawData.append(((RFieldData) metric).getData().get(inMatchTitle).get(0).toString()).append(ending(i, team.getTabs())); + } /* * Now, add the overview statistics to the team if the metric has overview statistics @@ -283,8 +290,9 @@ else if(metric instanceof RChooser) { else if(metric instanceof RStopwatch) overview.append("Stopwatch: ").append(metric.getTitle()).append(" Average: ").append(Utils.round(average, 2)).append(" Min: ").append(min).append(" Max: ").append(max); else if(metric instanceof RTextfield) overview.append("Textfield: ").append(metric.getTitle()).append(" Average chars: ").append(Utils.round(average, 2)).append(" Min: ").append(min).append(" Max: ").append(max); else if(metric instanceof RGallery) overview.append("Gallery: ").append(metric.getTitle()).append(" Average images: ").append(Utils.round(average, 2)).append(" Min: ").append(min).append(" Max: ").append(max); - else if(metric instanceof RChooser) overview.append("RChooser: ").append(metric.getTitle()); - else if(metric instanceof RCheckbox) overview.append("RCheckbox: ").append(metric.getTitle()); + else if(metric instanceof RChooser) overview.append("Chooser: ").append(metric.getTitle()); + else if(metric instanceof RCheckbox) overview.append("Checkbox: ").append(metric.getTitle()); + else if(metric instanceof RFieldData) overview.append("Field data: ").append(inMatchTitle); /* * Now append the raw data as processed above diff --git a/app/src/main/java/com/cpjd/roblu/ui/tutorials/Tutorial.java b/app/src/main/java/com/cpjd/roblu/ui/tutorials/Tutorial.java index bf23f12..c0d6da6 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/tutorials/Tutorial.java +++ b/app/src/main/java/com/cpjd/roblu/ui/tutorials/Tutorial.java @@ -43,7 +43,6 @@ protected void onCreate(Bundle savedInstanceState) { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); if(getSupportActionBar() != null) { - getSupportActionBar().setSubtitle("Tutorials for Roblu Master"); getSupportActionBar().setTitle("Tutorials"); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/app/src/main/java/com/cpjd/roblu/utils/Constants.java b/app/src/main/java/com/cpjd/roblu/utils/Constants.java index 340f4a1..3cf9c91 100644 --- a/app/src/main/java/com/cpjd/roblu/utils/Constants.java +++ b/app/src/main/java/com/cpjd/roblu/utils/Constants.java @@ -14,7 +14,7 @@ public abstract class Constants { public static final String SERVICE_ID = "com.cpjd.roblu.service"; public static final String RESTART_BROADCAST = "com.cpjd.roblu.service.RestartService"; - public static final String UPDATE_MESSAGE = "-Added Bluetooth syncing\n-Added divider, field diagram, and calculation metrics\n-Added form previewing\n-And much, much more!"; + public static final String UPDATE_MESSAGE = "-Bug fixes"; public static final int VERSION = 11; // used for updating the changelist diff --git a/app/src/main/java/com/cpjd/roblu/utils/Utils.java b/app/src/main/java/com/cpjd/roblu/utils/Utils.java index c8d1d05..4d0fced 100644 --- a/app/src/main/java/com/cpjd/roblu/utils/Utils.java +++ b/app/src/main/java/com/cpjd/roblu/utils/Utils.java @@ -35,6 +35,7 @@ import com.cpjd.roblu.models.metrics.RCheckbox; import com.cpjd.roblu.models.metrics.RChooser; import com.cpjd.roblu.models.metrics.RCounter; +import com.cpjd.roblu.models.metrics.RFieldData; import com.cpjd.roblu.models.metrics.RMetric; import com.cpjd.roblu.models.metrics.RSlider; import com.cpjd.roblu.models.metrics.RStopwatch; @@ -163,6 +164,19 @@ public static void setCursorColor(AppCompatEditText view, @ColorInt int color) { } } + public static String[] depackFieldData(RFieldData fieldData) { + String[] values = null; + if(fieldData.getData() != null) { + values = new String[fieldData.getData().size()]; + int index = 0; + for(Object key : fieldData.getData().keySet()) { + values[index] = key.toString(); + index++; + } + } + return values; + } + /** * Rounds a decimal to the specified number of digits (right of decimal point) * @param value the value to round diff --git a/app/src/main/res/drawable-xxhdpi/row_border.xml b/app/src/main/res/drawable-xxhdpi/row_border.xml new file mode 100644 index 0000000..c7b7b24 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/row_border.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_csv.xml b/app/src/main/res/layout/activity_csv.xml index fd02504..0c38e99 100644 --- a/app/src/main/res/layout/activity_csv.xml +++ b/app/src/main/res/layout/activity_csv.xml @@ -113,10 +113,16 @@ android:text="Our matches" android:layout_width="wrap_content" android:layout_height="wrap_content"/> + diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml index 153feb5..039336b 100644 --- a/app/src/main/res/layout/activity_setup.xml +++ b/app/src/main/res/layout/activity_setup.xml @@ -270,6 +270,9 @@ android:layout_width="200dp" android:layout_height="wrap_content" android:layout_centerHorizontal="true" + android:maxLines="1" + android:lines="1" + android:singleLine="true" android:layout_centerVertical="true" android:id="@+id/team_code_input" android:layout_below="@id/team_code"/> diff --git a/app/src/main/res/layout/submetric_import.xml b/app/src/main/res/layout/submetric_import.xml new file mode 100644 index 0000000..f3907d8 --- /dev/null +++ b/app/src/main/res/layout/submetric_import.xml @@ -0,0 +1,31 @@ + + + + +