diff --git a/app/build.gradle b/app/build.gradle index 651d9cc..e3aee32 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "com.cpjd.roblu" minSdkVersion 19 targetSdkVersion 27 - versionCode 46 - versionName "4.0.6" + versionCode 47 + versionName "4.1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" javaCompileOptions { diff --git a/app/libs/RobluCloud-API.jar b/app/libs/RobluCloud-API.jar index 27dbedd..476cd35 100644 Binary files a/app/libs/RobluCloud-API.jar and b/app/libs/RobluCloud-API.jar differ 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 3e99f76..4c4dc12 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java +++ b/app/src/main/java/com/cpjd/roblu/csv/ExportCSVTask.java @@ -17,7 +17,6 @@ import com.cpjd.roblu.models.RTeam; import com.cpjd.roblu.ui.teams.TeamsView; -import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.xssf.usermodel.XSSFSheet; @@ -109,11 +108,11 @@ public interface ExportCSVListener { private HashMap sheets; public static class SHEETS { - public static int MATCH_DATA = 0; - public static int PIT_DATA = 1; - public static int MATCH_LIST = 2; - public static int MATCH_LOOKUP = 3; - public static int OUR_MATCHES = 4; + static int MATCH_DATA = 0; + static int PIT_DATA = 1; + static int MATCH_LIST = 2; + static int MATCH_LOOKUP = 3; + static int OUR_MATCHES = 4; } public static class VERBOSENESS { @@ -128,8 +127,16 @@ public static class VERBOSENESS { */ private int verboseness; + /** + * Specifies the file type + */ private boolean isXslx; + /** + * Specifies the file name of the file + */ + private String fileName; + /** * Initializes the ExportCSVTask. * @param context context object @@ -142,6 +149,7 @@ public ExportCSVTask(Context context, ExportCSVListener listener, REvent event, this.listener = listener; this.verboseness = verboseness; this.isXslx = isXslx; + this.fileName = fileName; /* * @@ -246,18 +254,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()); } @@ -272,17 +280,32 @@ private void threadCompleted(String name) { threadsComplete++; if(threadsComplete == enabledSheets) { - File file = new IO(contextWeakReference.get()).getNewCSVExportFile(name + (isXslx ? ".xslx" : ".csv")); + File file = new IO(contextWeakReference.get()).getNewCSVExportFile(fileName + ".xslx"); + try { FileOutputStream out = new FileOutputStream(file); workbook.write(out); + Log.d("RBS", "Successfully generated .xslx file: "+file.getAbsolutePath()); + try { - if(isXslx) new ToCSV().convertExcelToCSV(file.getPath(), file.getPath()); - } catch(InvalidFormatException e) { + if(!isXslx) { + new ToCSV().convertExcelToCSV(file.getPath(), file.getParentFile().getPath()); + // Get the new file reference + file = new File(file.getParentFile()+File.separator+fileName+".csv"); + Log.d("RBS", "Converted .xslx to .CSV: "+file.getAbsolutePath()+" Check: "+file.exists()); + } + } catch(Exception e) { + Log.d("RBS", "Failed to convert the file to .CSV"); + listener.errorOccurred("Failed to generate "); } + // List contents of file + for(File f : file.getParentFile().listFiles()) { + Log.d("RBS", "Exports dir contains "+f.getAbsolutePath()); + } + out.close(); listener.csvFileGenerated(file); } catch(IOException e) { diff --git a/app/src/main/java/com/cpjd/roblu/csv/ToCSV.java b/app/src/main/java/com/cpjd/roblu/csv/ToCSV.java index f40e2f6..dcddbb6 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/ToCSV.java +++ b/app/src/main/java/com/cpjd/roblu/csv/ToCSV.java @@ -1,5 +1,3 @@ -package com.cpjd.roblu.csv; - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -17,6 +15,11 @@ Licensed to the Apache Software Foundation (ASF) under one or more limitations under the License. ==================================================================== */ +package com.cpjd.roblu.csv; + + +import android.util.Log; + import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.DataFormatter; @@ -171,22 +174,22 @@ public class ToCSV { * @param strDestination An instance of the String class encapsulating the * name of and path to a folder that will contain the resulting CSV * files. - * @throws java.io.FileNotFoundException Thrown if any file cannot be located + * @throws FileNotFoundException Thrown if any file cannot be located * on the filesystem during processing. - * @throws java.io.IOException Thrown if the filesystem encounters any + * @throws IOException Thrown if the filesystem encounters any * problems during processing. - * @throws java.lang.IllegalArgumentException Thrown if the values passed + * @throws IllegalArgumentException Thrown if the values passed * to the strSource parameter refers to a file or folder that does not * exist or if the value passed to the strDestination paramater refers * to a folder that does not exist or simply does not refer to a * folder. - * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown + * @throws InvalidFormatException Thrown * if the xml markup encountered whilst parsing a SpreadsheetML * file (.xlsx) is invalid. */ public void convertExcelToCSV(String strSource, String strDestination) - throws FileNotFoundException, IOException, - IllegalArgumentException, InvalidFormatException { + throws FileNotFoundException, IOException, + IllegalArgumentException, InvalidFormatException { // Simply chain the call to the overloaded convertExcelToCSV(String, // String, String, int) method, pass the default separator and ensure @@ -215,23 +218,23 @@ public void convertExcelToCSV(String strSource, String strDestination) * @param separator An instance of the String class that encapsulates the * character or characters the client wishes to use as the field * separator. - * @throws java.io.FileNotFoundException Thrown if any file cannot be located + * @throws FileNotFoundException Thrown if any file cannot be located * on the filesystem during processing. - * @throws java.io.IOException Thrown if the filesystem encounters any + * @throws IOException Thrown if the filesystem encounters any * problems during processing. - * @throws java.lang.IllegalArgumentException Thrown if the values passed + * @throws IllegalArgumentException Thrown if the values passed * to the strSource parameter refers to a file or folder that does not * exist or if the value passed to the strDestination paramater refers * to a folder that does not exist or simply does not refer to a * folder. - * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown + * @throws InvalidFormatException Thrown * if the xml markup encounetered whilst parsing a SpreadsheetML * file (.xlsx) is invalid. */ public void convertExcelToCSV(String strSource, String strDestination, String separator) - throws FileNotFoundException, IOException, - IllegalArgumentException, InvalidFormatException { + throws FileNotFoundException, IOException, + IllegalArgumentException, InvalidFormatException { // Simply chain the call to the overloaded convertExcelToCSV(String, // String, String, int) method and ensure that certain embedded @@ -261,11 +264,11 @@ public void convertExcelToCSV(String strSource, String strDestination, * @param separator An instance of the String class encapsulating the * characters or characters that should be used to separate items * on a line within the CSV file. - * @throws java.io.FileNotFoundException Thrown if any file cannot be located + * @throws FileNotFoundException Thrown if any file cannot be located * on the filesystem during processing. - * @throws java.io.IOException Thrown if the filesystem encounters any + * @throws IOException Thrown if the filesystem encounters any * problems during processing. - * @throws java.lang.IllegalArgumentException Thrown if the values passed + * @throws IllegalArgumentException Thrown if the values passed * to the strSource parameter refers to a file or folder that does not * exist, if the value passed to the strDestination paramater refers * to a folder that does not exist, if the value passed to the @@ -273,14 +276,14 @@ public void convertExcelToCSV(String strSource, String strDestination, * value passed to the formattingConvention parameter is other than * one of the values defined by the constants ToCSV.EXCEL_STYLE_ESCAPING * and ToCSV.UNIX_STYLE_ESCAPING. - * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown + * @throws InvalidFormatException Thrown * if the xml markup encounetered whilst parsing a SpreadsheetML * file (.xlsx) is invalid. */ public void convertExcelToCSV(String strSource, String strDestination, String separator, int formattingConvention) - throws FileNotFoundException, IOException, - IllegalArgumentException, InvalidFormatException { + throws FileNotFoundException, IOException, + IllegalArgumentException, InvalidFormatException { File source = new File(strSource); File destination = new File(strDestination); File[] filesList; @@ -307,7 +310,7 @@ public void convertExcelToCSV(String strSource, String strDestination, // Ensure the value passed to the formattingConvention parameter is // within range. if(formattingConvention != ToCSV.EXCEL_STYLE_ESCAPING && - formattingConvention != ToCSV.UNIX_STYLE_ESCAPING) { + formattingConvention != ToCSV.UNIX_STYLE_ESCAPING) { throw new IllegalArgumentException("The value passed to the " + "formattingConvention parameter is out of range."); } @@ -346,17 +349,17 @@ public void convertExcelToCSV(String strSource, String strDestination, for(File excelFile : filesList) { // Open the workbook this.openWorkbook(excelFile); - + // Convert it's contents into a CSV file this.convertToCSV(); - + // Build the name of the csv folder from that of the Excel workbook. // Simply replace the .xls or .xlsx file extension with .csv destinationFilename = excelFile.getName(); destinationFilename = destinationFilename.substring( 0, destinationFilename.lastIndexOf(".")) + ToCSV.CSV_FILE_EXTENSION; - + // Save the CSV file away using the newly constricted file name // and to the specified directory. this.saveCSVFile(new File(destination, destinationFilename)); @@ -370,14 +373,14 @@ public void convertExcelToCSV(String strSource, String strDestination, * @param file An instance of the File class that encapsulates a handle * to a valid Excel workbook. Note that the workbook can be in * either binary (.xls) or SpreadsheetML (.xlsx) format. - * @throws java.io.FileNotFoundException Thrown if the file cannot be located. - * @throws java.io.IOException Thrown if a problem occurs in the file system. - * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown + * @throws FileNotFoundException Thrown if the file cannot be located. + * @throws IOException Thrown if a problem occurs in the file system. + * @throws InvalidFormatException Thrown * if invalid xml is found whilst parsing an input SpreadsheetML * file. */ private void openWorkbook(File file) throws FileNotFoundException, - IOException, InvalidFormatException { + IOException, InvalidFormatException { FileInputStream fis = null; try { System.out.println("Opening workbook [" + file.getName() + "]"); @@ -443,12 +446,12 @@ private void convertToCSV() { * * @param file An instance of the File class that encapsulates a handle * referring to the CSV file. - * @throws java.io.FileNotFoundException Thrown if the file cannot be found. - * @throws java.io.IOException Thrown to indicate and error occurred in the + * @throws FileNotFoundException Thrown if the file cannot be found. + * @throws IOException Thrown to indicate and error occurred in the * underylying file system. */ private void saveCSVFile(File file) - throws FileNotFoundException, IOException { + throws FileNotFoundException, IOException { FileWriter fw; BufferedWriter bw = null; ArrayList line; @@ -456,6 +459,8 @@ private void saveCSVFile(File file) String csvLineElement; try { + Log.d("RBS", "Saving CSV FILE "+file.getAbsolutePath()); + System.out.println("Saving the CSV file [" + file.getName() + "]"); // Open a writer onto the CSV file. @@ -623,7 +628,7 @@ private String escapeEmbeddedCharacters(String field) { // with speech marks. buffer = new StringBuffer(field); if((buffer.indexOf(this.separator)) > -1 || - (buffer.indexOf("\n")) > -1) { + (buffer.indexOf("\n")) > -1) { buffer.insert(0, "\""); buffer.append("\""); } @@ -688,31 +693,31 @@ else if(args.length == 4) { // The Source File/Folder, Destination Folder, Separator and // Formatting Convnetion were passed to the main method. converter.convertExcelToCSV(args[0], args[1], - args[2], Integer.parseInt(args[3])); + args[2], Integer.parseInt(args[3])); } else { // None or more than four parameters were passed so display //a Usage message. System.out.println("Usage: java ToCSV [Source File/Folder] " + - "[Destination Folder] [Separator] [Formatting Convention]\n" + - "\tSource File/Folder\tThis argument should contain the name of and\n" + - "\t\t\t\tpath to either a single Excel workbook or a\n" + - "\t\t\t\tfolder containing one or more Excel workbooks.\n" + - "\tDestination Folder\tThe name of and path to the folder that the\n" + - "\t\t\t\tCSV files should be written out into. The\n" + - "\t\t\t\tfolder must exist before running the ToCSV\n" + - "\t\t\t\tcode as it will not check for or create it.\n" + - "\tSeparator\t\tOptional. The character or characters that\n" + - "\t\t\t\tshould be used to separate fields in the CSV\n" + - "\t\t\t\trecord. If no value is passed then the comma\n" + - "\t\t\t\twill be assumed.\n" + - "\tFormatting Convention\tOptional. This argument can take one of two\n" + - "\t\t\t\tvalues. Passing 0 (zero) will result in a CSV\n" + - "\t\t\t\tfile that obeys Excel's formatting conventions\n" + - "\t\t\t\twhilst passing 1 (one) will result in a file\n" + - "\t\t\t\tthat obeys UNIX formatting conventions. If no\n" + - "\t\t\t\tvalue is passed, then the CSV file produced\n" + - "\t\t\t\twill obey Excel's formatting conventions."); + "[Destination Folder] [Separator] [Formatting Convention]\n" + + "\tSource File/Folder\tThis argument should contain the name of and\n" + + "\t\t\t\tpath to either a single Excel workbook or a\n" + + "\t\t\t\tfolder containing one or more Excel workbooks.\n" + + "\tDestination Folder\tThe name of and path to the folder that the\n" + + "\t\t\t\tCSV files should be written out into. The\n" + + "\t\t\t\tfolder must exist before running the ToCSV\n" + + "\t\t\t\tcode as it will not check for or create it.\n" + + "\tSeparator\t\tOptional. The character or characters that\n" + + "\t\t\t\tshould be used to separate fields in the CSV\n" + + "\t\t\t\trecord. If no value is passed then the comma\n" + + "\t\t\t\twill be assumed.\n" + + "\tFormatting Convention\tOptional. This argument can take one of two\n" + + "\t\t\t\tvalues. Passing 0 (zero) will result in a CSV\n" + + "\t\t\t\tfile that obeys Excel's formatting conventions\n" + + "\t\t\t\twhilst passing 1 (one) will result in a file\n" + + "\t\t\t\tthat obeys UNIX formatting conventions. If no\n" + + "\t\t\t\tvalue is passed, then the CSV file produced\n" + + "\t\t\t\twill obey Excel's formatting conventions."); converted = false; } } @@ -729,10 +734,10 @@ else if(args.length == 4) { ex.printStackTrace(System.out); converted = false; } - + if (converted) { - System.out.println("Conversion took " + - (int)((System.currentTimeMillis() - startTime)/1000) + " seconds"); + System.out.println("Conversion took " + + (int)((System.currentTimeMillis() - startTime)/1000) + " seconds"); } } @@ -769,4 +774,4 @@ public boolean accept(File file, String name) { return(name.endsWith(".xls") || name.endsWith(".xlsx")); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/cpjd/roblu/csv/csvSheets/Lookup.java b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/Lookup.java index 58d1065..c09f046 100644 --- a/app/src/main/java/com/cpjd/roblu/csv/csvSheets/Lookup.java +++ b/app/src/main/java/com/cpjd/roblu/csv/csvSheets/Lookup.java @@ -7,6 +7,7 @@ import com.cpjd.roblu.models.metrics.RMetric; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.usermodel.XSSFSheet; import java.util.ArrayList; @@ -15,31 +16,35 @@ public class Lookup extends CSVSheet { @Override public void generateSheet(XSSFSheet sheet, REvent event, RForm form, RTeam[] teams, ArrayList checkouts) { - Row one = createRow(sheet); one.createCell(0).setCellValue("Match Number"); Row two = createRow(sheet); two.createCell(0).setCellValue(1); + int maxListLength = checkouts.size(); + // Team's row - two.createCell(4).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,2,FALSE)"); - two.createCell(5).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,3,FALSE)"); - two.createCell(6).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,4,FALSE)"); - two.createCell(7).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,5,FALSE)"); - two.createCell(8).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,6,FALSE)"); - two.createCell(9).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$81,7,FALSE)"); + two.createCell(4).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",2,FALSE)"); + two.createCell(5).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",3,FALSE)"); + two.createCell(6).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",4,FALSE)"); + two.createCell(7).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",5,FALSE)"); + two.createCell(8).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",6,FALSE)"); + two.createCell(9).setCellFormula("VLOOKUP($A$2,MatchList!$A$2:$G$"+maxListLength+",7,FALSE)"); + + int maxWidth = form.getMatch().size() + 2; + String columnLetter = CellReference.convertNumToColString(maxWidth); // Load metrics int index = 0; for(RMetric metric : form.getMatch()) { Row row = createRow(sheet); createCell(row, 3, metric.getTitle()); - row.createCell(4).setCellFormula("VLOOKUP(E$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); - row.createCell(5).setCellFormula("VLOOKUP(F$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); - row.createCell(6).setCellFormula("VLOOKUP(G$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); - row.createCell(7).setCellFormula("VLOOKUP(H$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); - row.createCell(8).setCellFormula("VLOOKUP(I$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); - row.createCell(9).setCellFormula("VLOOKUP(J$2,'MatchData'!$A$2:$S$41,"+(index + 3)+",FALSE)"); + row.createCell(4).setCellFormula("VLOOKUP(E$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); + row.createCell(5).setCellFormula("VLOOKUP(F$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); + row.createCell(6).setCellFormula("VLOOKUP(G$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); + row.createCell(7).setCellFormula("VLOOKUP(H$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); + row.createCell(8).setCellFormula("VLOOKUP(I$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); + row.createCell(9).setCellFormula("VLOOKUP(J$2,'MatchData'!$A$2:$"+columnLetter+"$"+maxListLength+","+(index + 3)+",FALSE)"); index++; } } 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 b69a9c6..85388f8 100644 --- a/app/src/main/java/com/cpjd/roblu/io/IO.java +++ b/app/src/main/java/com/cpjd/roblu/io/IO.java @@ -456,9 +456,8 @@ public RBackup convertBackupFile(Uri toCopy) { */ public File getNewCSVExportFile(String name) { File f = new File(context.getCacheDir(), PREFIX+File.separator+"exports"+File.separator+name); - if(f.exists()) { - if(!f.delete()) Log.d("RBS", "Failed to delete old cached csv export file."); - } + delete(f.getParentFile()); + if(f.getParentFile().mkdirs()) Log.d("RBS", "Successfully created temporary .csv export directory."); try { if(f.createNewFile()) Log.d("RBS", "File created successfully."); @@ -661,7 +660,7 @@ public byte[] loadPicture(int eventID, int pictureID) { * @return the ID of a new picture. */ private int getNewPictureID(int eventID) { - File f = new File(context.getFilesDir(), PREFIX+File.separator+"events"+eventID+File.separator+"images"+File.separator); + File f = new File(context.getFilesDir(), PREFIX+File.separator+"events"+File.separator+eventID+File.separator+"images"+File.separator); if(!f.exists()) if(f.mkdirs()) Log.d("RBS", "Successfully created /events/images directory."); int maxID = 0; File[] children = f.listFiles(); diff --git a/app/src/main/java/com/cpjd/roblu/models/RCloudSettings.java b/app/src/main/java/com/cpjd/roblu/models/RCloudSettings.java index 755cbc7..8256b55 100644 --- a/app/src/main/java/com/cpjd/roblu/models/RCloudSettings.java +++ b/app/src/main/java/com/cpjd/roblu/models/RCloudSettings.java @@ -33,4 +33,13 @@ public class RCloudSettings implements Serializable { * If true, keep sending the /checkouts/purge request to the server */ private boolean purgeRequested; + /** + * Used for authenticating read only events + */ + private int publicTeamNumber; + + /** + * Just stores a local copy of the optedIn status to keep the UI correct + */ + private boolean optedIn; } 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 d103519..16e0755 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 @@ -69,16 +69,22 @@ public RMetric clone() { * @return the value */ public String getValue(ArrayList metrics) { + if(calculation == null || calculation.equals("null")) return "Bad equation"; + try { String equation = calculation; // Substitute values in for the metric names for(RMetric metric : metrics) { + // Skip the reference to "ourself" + if(metric.getTitle().equals(title)) continue; + if(metric instanceof RCounter || metric instanceof RStopwatch || metric instanceof RSlider) { equation = equation.replaceAll(metric.getTitle(), metric.toString()); } else if(metric instanceof RCalculation) { - equation = equation.replaceAll(metric.getTitle(), ((RCalculation) metric).getValue(metrics)); + // This condition is required or this will overflow recursively + if(equation.contains(metric.getTitle())) equation = equation.replaceAll(metric.getTitle(), ((RCalculation) metric).getValue(metrics)); } } 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 f37183e..bfa10ff 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 @@ -145,17 +145,25 @@ public void messageReceived(String header, String message) { for(RMetric downloadedMetric : downloadedTab.getMetrics()) { for(RMetric localMetric : localTab.getMetrics()) { - // Found the metric, determine if a merge needs to occur + // Found the metric, determine if a merge needs to occur if(downloadedMetric.getID() == localMetric.getID()) { /* * We have to deal with one special case scenario - the gallery. * The gallery should never be overrided, just added to */ - if(downloadedMetric instanceof RGallery && localMetric instanceof RGallery && ((RGallery) localMetric).getImages() != null && ((RGallery) downloadedMetric).getImages() != null) { - ((RGallery) localMetric).getImages().addAll(((RGallery) downloadedMetric).getImages()); + if(downloadedMetric instanceof RGallery && localMetric instanceof RGallery) { + if(((RGallery) localMetric).getPictureIDs() == null) ((RGallery) localMetric).setPictureIDs(new ArrayList()); + if(((RGallery) downloadedMetric).getImages() != null) { + // Add images to the current gallery + for(int j = 0; j < ((RGallery) downloadedMetric).getImages().size(); j++) { + ((RGallery) localMetric).getPictureIDs().add(io.savePicture(event.getID(), ((RGallery) downloadedMetric).getImages().get(j))); + } + } + // Don't forget to clear the pictures from memory after they've been merged + ((RGallery) downloadedMetric).setImages(null); } // If the local metric is already edited, keep whichever data is newest - if(localMetric.isModified()) { + else if(localMetric.isModified()) { if(checkout.getTeam().getLastEdit() >= team.getLastEdit()) { int replaceIndex = localTab.getMetrics().indexOf(localMetric); localTab.getMetrics().set(replaceIndex, downloadedMetric); @@ -268,6 +276,24 @@ public void messageReceived(String header, String message) { } } + /* + * Load images from local disk into each checkout, this will spike memory temporarily while uploading + */ + for(RCheckout checkout : checkouts) { + for(RTab tab : checkout.getTeam().getTabs()) { + for(RMetric metric : tab.getMetrics()) { + if(metric instanceof RGallery) { + ((RGallery) metric).setImages(new ArrayList()); + if(((RGallery) metric).getPictureIDs() != null) { + for(int ID : ((RGallery) metric).getPictureIDs()) { + ((RGallery) metric).addImage(io.loadPicture(event.getID(), ID)); + } + } + } + } + } + } + try { Log.d("RBS", "Generated "+checkouts.size()+" checkouts. Here's what is should look like: "+mapper.writeValueAsString(checkouts)); 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 1883a9d..973abc1 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 @@ -43,6 +43,8 @@ public class EventDepacker extends AsyncTask { private IO io; + private int teamNumber; + @Setter private EventDepackerListener listener; @@ -51,8 +53,9 @@ public interface EventDepackerListener { void success(REvent event); } - public EventDepacker(IO io) { + public EventDepacker(IO io, int teamNumber) { this.io = io; + this.teamNumber = teamNumber; } @Override @@ -68,12 +71,22 @@ protected Void doInBackground(Void... params) { cloudSettings.setLastCheckoutSync(0); cloudSettings.setLastTeamSync(0); cloudSettings.setPurgeRequested(false); + cloudSettings.setPublicTeamNumber(teamNumber); io.saveCloudSettings(cloudSettings); Request r = new Request(settings.getServerIP()); CloudTeamRequest ctr = new CloudTeamRequest(r, settings.getCode()); + + if(teamNumber != -1) { + ctr.setCode(""); + ctr.setTeamNumber(teamNumber); + } CloudCheckoutRequest ccr = new CloudCheckoutRequest(r, settings.getCode()); + if(teamNumber != -1) { + ccr.setTeamCode(""); + ccr.setTeamNumber(teamNumber); + } - if(settings.getCode() == null || settings.getCode().equals("")) { + if(teamNumber == -1 && (settings.getCode() == null || settings.getCode().equals(""))) { if(listener != null) listener.errorOccurred("No team code found in settings. Unable to import event."); return null; } @@ -89,19 +102,12 @@ protected Void doInBackground(Void... params) { return null; } - if(settings.getCode() == null || settings.getCode().equals("")) { - if(listener != null) listener.errorOccurred("Please enter a team code to download events."); - return null; - } - /* * Download everything */ CloudTeam team = ctr.getTeam(-1); CloudCheckout[] pulledCheckouts = ccr.pullCheckouts(0); - Log.d("RBS", "Pulled "+pulledCheckouts.length+" checkouts"); - // Get a new event ID REvent event = new REvent(io.getNewEventID(), team.getActiveEventName()); event.setKey(team.getTbaKey()); 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 bb1436a..b15eae7 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 @@ -81,6 +81,7 @@ protected Boolean doInBackground(Void... params) { RSettings settings = io.loadSettings(); RCloudSettings cloudSettings = io.loadCloudSettings(); cloudSettings.setPurgeRequested(false); + cloudSettings.setPublicTeamNumber(-1); io.saveCloudSettings(cloudSettings); io.saveSettings(settings); Request r = new Request(settings.getServerIP()); 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 d0ee740..281ccac 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 @@ -90,7 +90,15 @@ public void loop() { Request r = new Request(settings.getServerIP()); ObjectMapper mapper = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); CloudTeamRequest teamRequest = new CloudTeamRequest(r, settings.getCode()); + if(cloudSettings.getPublicTeamNumber() != -1) { + teamRequest.setTeamNumber(cloudSettings.getPublicTeamNumber()); + teamRequest.setCode(""); + } CloudCheckoutRequest checkoutRequest = new CloudCheckoutRequest(r, settings.getCode()); + if(cloudSettings.getPublicTeamNumber() != -1) { + checkoutRequest.setTeamNumber(cloudSettings.getPublicTeamNumber()); + checkoutRequest.setTeamCode(""); + } boolean result = r.ping(); if(result) Utils.requestServerHealthRefresh(getApplicationContext(), "online"); @@ -151,7 +159,9 @@ public void loop() { try { CloudTeam t = teamRequest.getTeam(cloudSettings.getLastTeamSync()); settings.setRui(mapper.readValue(t.getUi(), RUI.class)); + cloudSettings.setOptedIn(t.isOptedIn()); cloudSettings.setLastTeamSync(System.currentTimeMillis()); + io.saveCloudSettings(cloudSettings); Log.d("RBS-Service", "Successfully downloaded RUI"); } catch(Exception e) { Log.d("RBS-Service", "Failed to download an RUI from the server."); @@ -179,11 +189,12 @@ public void loop() { form = mapper.readValue(t.getForm(), RForm.class); form.setUploadRequired(false); io.saveForm(activeEvent.getID(), form); + cloudSettings.setOptedIn(t.isOptedIn()); cloudSettings.setLastTeamSync(System.currentTimeMillis()); io.saveCloudSettings(cloudSettings); Log.d("RBS-Service", "Successfully downloaded a form"); } catch(Exception e) { - Log.d("RBS-Service", "Failed to download an RForm from the server."); + Log.d("RBS-Service", "Failed to download an RForm from the server: "+e.getMessage()); } } @@ -198,7 +209,6 @@ public void loop() { Log.d("RBS-Service", "Checking for completed checkouts..."); long maxTimestamp = 0; CloudCheckout[] checkouts = checkoutRequest.pullCompletedCheckouts(cloudSettings.getLastCheckoutSync()); - Log.d("RBS-Service", "Pulled: "+checkouts.length); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS", Locale.getDefault()); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // server runs on UTC @@ -307,12 +317,17 @@ else if(localMetric.isModified()) { Log.d("RBS-Service", "Merged team: "+checkout.getTeam().getName()); - Notify.notifyMerged(getApplicationContext(), activeEvent.getID(), checkout); + // Prevent spamming the user with notifications + if(checkouts.length < 6) Notify.notifyMerged(getApplicationContext(), activeEvent.getID(), checkout); // Notify the TeamViewer in case Roblu Master is viewing the data that was just modified Utils.requestTeamViewerRefresh(getApplicationContext(), team.getName()); } + if(checkouts.length >= 6) { + Notify.notifyNoAction(getApplicationContext(), "Merged scouting data", "Merged "+checkouts.length+" checkouts."); + } + if(checkouts != null && checkouts.length > 0) { cloudSettings.setLastCheckoutSync(maxTimestamp); io.saveCloudSettings(cloudSettings); 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 f068f88..10e9489 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 @@ -1,15 +1,24 @@ package com.cpjd.roblu.ui.events; +import android.app.AlertDialog; +import android.app.Dialog; import android.app.ProgressDialog; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.os.StrictMode; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.AppCompatEditText; import android.support.v7.widget.Toolbar; +import android.text.InputFilter; +import android.text.InputType; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; @@ -18,6 +27,7 @@ import com.cpjd.roblu.io.IO; import com.cpjd.roblu.models.RBackup; import com.cpjd.roblu.models.REvent; +import com.cpjd.roblu.models.RSettings; import com.cpjd.roblu.models.RTeam; import com.cpjd.roblu.models.RUI; import com.cpjd.roblu.sync.cloud.EventDepacker; @@ -49,11 +59,8 @@ public class EventCreateMethodPicker extends AppCompatActivity implements Adapte /* * Items on the list and their descriptions */ - private final String items[] = { "Import from TheBlueAlliance.com", "Import from Roblu Cloud", "Import from backup file", "Create event"}; - private final String sub_items[] = {"Import the event from an online database.", "Import an event from Roblu Cloud for use with multiple Roblu Master apps", "Import the event and all information from a previously exported backup file.","Create the event manually."}; - - //private long tempEventID; - //private Event tempEvent; + private final String items[] = { "Import from TheBlueAlliance.com", "Import from Roblu Cloud", "Import read only from Roblu Cloud", "Import from backup file", "Create event"}; + private final String sub_items[] = {"Import the event from an online database.", "Import an event from Roblu Cloud for use with multiple Roblu Master apps", "View and access the scouting data of a different team who has opted for publicly available scouting information.", "Import the event and all information from a previously exported backup file.","Create the event manually."}; /** * Reference to the user's color and UI preferences @@ -127,7 +134,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) /* * User selected manual event creation */ - if(position == 3) { + if(position == 4) { startActivityForResult(new Intent(this, EventEditor.class), Constants.GENERAL); } /* @@ -155,7 +162,7 @@ else if(position == 1) { .setFastDialogListener(new FastDialogBuilder.FastDialogListener() { @Override public void accepted() { - importRobluCloudEvent(); + importRobluCloudEvent(-1); } @Override @@ -164,12 +171,18 @@ public void denied() {} @Override public void neutral() {} }).build(EventCreateMethodPicker.this); - } else importRobluCloudEvent(); + } else importRobluCloudEvent(-1); } /* - * User selected import from backup file + * User selected the read only event option, first get their username */ else if(position == 2) { + importPublicRobluCloudEvent(); + } + /* + * User selected import from backup file + */ + else if(position == 3) { /* * 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 @@ -189,25 +202,87 @@ else if(position == 2) { /** * Imports an event from Roblu Cloud, given one exists + * + * @param teamNumber -1 to disable */ - private void importRobluCloudEvent() { + private void importRobluCloudEvent(int teamNumber) { d = new ProgressDialog(EventCreateMethodPicker.this); d.setTitle("Get ready!"); d.setMessage("Roblu is importing an event from Roblu Cloud..."); d.setCancelable(false); d.show(); - // Stop the background service so it won't interfere IntentFilter serviceFilter = new IntentFilter(); serviceFilter.addAction(Constants.SERVICE_ID); Intent serviceIntent = new Intent(this, Service.class); stopService(serviceIntent); - EventDepacker dp = new EventDepacker(new IO(getApplicationContext())); + EventDepacker dp = new EventDepacker(new IO(getApplicationContext()), teamNumber); dp.setListener(this); dp.execute(); } + private void importPublicRobluCloudEvent() { + // check for an internet connection + if(!Utils.hasInternetConnection(getApplicationContext())) { + Utils.showSnackbar(findViewById(R.id.advsettings), getApplicationContext(), "You are not connected to the internet", true, 0); + return; + } + /* + * We need to make sure that this thread has access to the internet + */ + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build(); + StrictMode.setThreadPolicy(policy); + + final RSettings settings = new IO(getApplicationContext()).loadSettings(); + + RUI rui = settings.getRui(); + + AlertDialog.Builder builder = new AlertDialog.Builder(EventCreateMethodPicker.this); + LinearLayout layout = new LinearLayout(EventCreateMethodPicker.this); + layout.setOrientation(LinearLayout.VERTICAL); + + // this is the team code input edit text + final AppCompatEditText input = new AppCompatEditText(EventCreateMethodPicker.this); + Utils.setInputTextLayoutColor(rui.getAccent(),null, input); + input.setHighlightColor(rui.getAccent()); + input.setHintTextColor(rui.getText()); + input.setTextColor(rui.getText()); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + InputFilter[] FilterArray = new InputFilter[1]; + FilterArray[0] = new InputFilter.LengthFilter(30); + input.setFilters(FilterArray); + layout.addView(input); + + builder.setView(layout); + + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + importRobluCloudEvent(Integer.parseInt(input.getText().toString())); + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } + }); + TextView view = new TextView(EventCreateMethodPicker.this); + view.setTextSize(Utils.DPToPX(getApplicationContext(), 5)); + view.setPadding(Utils.DPToPX(getApplicationContext(), 18), Utils.DPToPX(getApplicationContext(), 18), Utils.DPToPX(getApplicationContext(), 18), Utils.DPToPX(getApplicationContext(), 18)); + view.setText("FRC team number:"); + view.setTextColor(rui.getText()); + AlertDialog dialog = builder.create(); + dialog.setCustomTitle(view); + if(dialog.getWindow() != null) { + dialog.getWindow().getAttributes().windowAnimations = rui.getAnimation(); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(rui.getBackground())); + } + dialog.show(); + dialog.getButton(Dialog.BUTTON_NEGATIVE).setTextColor(rui.getAccent()); + dialog.getButton(Dialog.BUTTON_POSITIVE).setTextColor(rui.getAccent()); + } + /** * Receives result data from child activities * @param requestCode the request code of the child activities 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 6b4a32f..0983149 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 @@ -219,7 +219,7 @@ else if(preference.getKey().equals("edit_event")) { */ else if(preference.getKey().equalsIgnoreCase("tba_sync")) { // Download the entire event - tbaSyncDialog = ProgressDialog.show(getActivity(), "Syncing event with TheBlueAlliance...", "This may take several seconds.", false); + tbaSyncDialog = ProgressDialog.show(getActivity(), "Syncing event with TheBlueAlliance...", "This may take several seconds...", false); tbaSyncDialog.setCancelable(false); new ImportEvent(new TBALoadEventsTask.LoadTBAEventsListener() { @Override 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 ca1f7f9..fbd1233 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 @@ -1,5 +1,6 @@ package com.cpjd.roblu.ui.forms; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; @@ -243,6 +244,7 @@ public void onCheckedChanged(RadioGroup radioGroup, int i) { * @param counter RCounter reference to be set to the UI * @return a UI CardView */ + @SuppressLint("ClickableViewAccessibility") public CardView getCounter(final RCounter counter) { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.CENTER_VERTICAL); @@ -277,19 +279,33 @@ public CardView getCounter(final RCounter counter) { addButton.setScaleY(1.5f); addButton.setLayoutParams(params); + final RelativeLayout layout = new RelativeLayout(activity); + final TextView number = new TextView(activity); + final TextView observed = new TextView(activity); + ImageView minusButton = new ImageView(activity); + final TimerTask task = new TimerTask() { + @Override + public void run() { + counter.add(); + number.setText(counter.getTextValue()); + listener.changeMade(counter); + } + }; + + + params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.LEFT_OF, addButton.getId()); params.addRule(RelativeLayout.CENTER_VERTICAL); - final TextView number = new TextView(activity); + number.setTextSize(25); number.setTextColor(rui.getText()); number.setId(Utils.generateViewId()); number.setText(String.valueOf(counter.getTextValue())); number.setLayoutParams(params); number.setPadding(Utils.DPToPX(activity, 20), number.getPaddingTop(), Utils.DPToPX(activity, 20), number.getPaddingBottom()); - final RelativeLayout layout = new RelativeLayout(activity); + // Observed field - final TextView observed = new TextView(activity); observed.setTextColor(rui.getText()); observed.setText(R.string.not_observed_yet); observed.setTextSize(10); @@ -316,7 +332,7 @@ public void onClick(View view) { params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.addRule(RelativeLayout.LEFT_OF, number.getId()); params.addRule(RelativeLayout.CENTER_VERTICAL); - ImageView minusButton = new ImageView(activity); + minusButton.setBackground(minus); minusButton.setId(Utils.generateViewId()); minusButton.setEnabled(editable); @@ -868,6 +884,7 @@ private void addStopwatchLapButton(final RStopwatch stopwatch, final RelativeLay final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); if(buttons.size() != 0) params.addRule(RelativeLayout.BELOW, buttons.get(buttons.size() - 1).getId()); else params.addRule(RelativeLayout.BELOW, playButton.getId()); + b.setTextColor(rui.getText()); b.setLayoutParams(params); b.setOnClickListener(new View.OnClickListener() { @Override @@ -1278,7 +1295,7 @@ public CardView generateLineChart(String metricName, LinkedHashMap values = new LinkedHashMap<>(); + if(team.getTabs().get(1).getMetrics().get(i) instanceof RDivider) { + layout.addView(new RMetricToUI(getActivity(), new IO(view.getContext()).loadSettings().getRui(), false).getDivider((RDivider)team.getTabs().get(1).getMetrics().get(i))); + continue; + } + // Process all the values for(int j = 1; j < team.getTabs().size(); j++) { RMetric metric = team.getTabs().get(j).getMetrics().get(i); - if(metric instanceof RDivider) { - layout.addView(new RMetricToUI(getActivity(), new IO(view.getContext()).loadSettings().getRui(), false).getDivider((RDivider)metric)); - continue; - } - if(metric instanceof RGallery) { galleries.add((RGallery)metric); continue; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7fc87a2..6fbbfce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ Only write observed metrics - Write not observed metrics, but only if the team is edited + Write not observed metrics, if team is edited Write all metrics, regardless of observed state diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7fe8e2f..05fac4b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -39,6 +39,11 @@ android:title="Purge cloud event" android:key="purge" android:summary="This will reset the server. ALL scouting data will be removed. You should purge events after you're done to save stop all devices from syncing needlessly."/> +