diff --git a/app/build.gradle b/app/build.gradle index bf50b6b..abd3dc8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.2.0' classpath 'com.google.gms:google-services:3.1.1' } } @@ -21,14 +21,14 @@ allprojects { } android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 28 + buildToolsVersion '28.0.2' defaultConfig { applicationId "com.cpjd.roblu" minSdkVersion 19 - targetSdkVersion 27 - versionCode 65 - versionName "4.5.7" + targetSdkVersion 28 + versionCode 66 + versionName "4.5.8" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" javaCompileOptions { @@ -94,20 +94,20 @@ dependencies { implementation 'com.roughike:bottom-bar:2.1.1' implementation 'com.miguelcatalan:materialsearchview:1.4.0' implementation 'pub.devrel:easypermissions:0.2.1' - implementation 'com.google.guava:guava:22.0-android' + implementation 'com.google.guava:guava:24.1-android' // ignore this warning implementation 'com.jrummyapps:colorpicker:2.1.6' implementation 'com.github.infotech-group:CanvasView:release' - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:recyclerview-v7:27.0.2' - implementation 'com.android.support:preference-v7:27.0.2' - implementation 'com.android.support:cardview-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' + implementation 'com.android.support:preference-v7:28.0.0' + implementation 'com.android.support:cardview-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' implementation 'pub.devrel:easypermissions:0.2.1' implementation 'com.squareup.picasso:picasso:2.5.2' implementation('com.mikepenz:aboutlibraries:5.9.5@aar') { transitive = true } - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' - implementation "org.projectlombok:lombok:1.16.16" - annotationProcessor 'org.projectlombok:lombok:1.16.16' + implementation "org.projectlombok:lombok:1.18.0" + annotationProcessor 'org.projectlombok:lombok:1.18.0' implementation 'javax.annotation:jsr250-api:1.0' } diff --git a/app/src/main/java/com/cpjd/roblu/models/RTab.java b/app/src/main/java/com/cpjd/roblu/models/RTab.java index 6fa74bf..fc1411e 100644 --- a/app/src/main/java/com/cpjd/roblu/models/RTab.java +++ b/app/src/main/java/com/cpjd/roblu/models/RTab.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.regex.Pattern; import lombok.Data; @@ -132,4 +133,38 @@ public boolean equals(Object other) { (((RTab) other).getMatchType() == this.matchType) && (((RTab) other).getMatchOrder() == this.matchOrder); } + + public boolean getSimpleMatchString(String match) { + try { + String adj = (title.toLowerCase().replaceAll("\\s+", "").replaceAll("Quals", "q").replaceAll("Quarters", "qu") + .replaceAll("Semis", "s").replaceAll("Finals", "f").replaceAll("Match", "m")).replace("predictions", "p"); + + if(match.equals("pit") && adj.equals("pit")) return true; + if(match.equals("p") && adj.equals("p")) return true; + + Pattern category = Pattern.compile("[a-zA-Z]+"); + Pattern specific = Pattern.compile("[a-zA-Z]+\\d+"); + Pattern specific2 = Pattern.compile("[a-zA-Z]+\\d+[a-zA-Z]+\\d+"); + + if(category.matcher(match).matches()) { + if(match.equals("qu") | match.equals("s")) match += "m"; + return adj.replaceAll("\\d+", "").equalsIgnoreCase(match); + } + else if(specific.matcher(match).matches() || specific2.matcher(match).matches()) + return adj.equalsIgnoreCase(match); + else { // range + int low = Integer.parseInt(match.substring(match.indexOf("(") + 1, match.indexOf("-"))); + int high = Integer.parseInt(match.substring(match.indexOf("-") + 1, match.indexOf(")"))); + + if(low > high || (low < 0 || high < 0)) return false; + + for(int i = low; i <= high; i++) { + if(match.replaceAll("\\(\\d+-\\d+\\)", String.valueOf(i)).equalsIgnoreCase(adj)) return true; + } + } + } catch(Exception e) { + e.printStackTrace(); + } + return false; + } } \ No newline at end of file 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 c4d19c3..180650c 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 @@ -4,9 +4,12 @@ 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.RMetric; import com.cpjd.roblu.ui.teamsSorting.TeamMetricProcessor; import com.cpjd.roblu.utils.Utils; +import com.cpjd.roblu.utils.searchCommands.CommandMetric; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -187,9 +190,63 @@ else if(filter == TeamsView.SORT_TYPE.SEARCH) { for(RTeam team : teams) { team.setCustomRelevance(0); team.setFilterTag(""); + } + + boolean metricFiltered = false; + try { + String[] tokens = query.split("\\s+"); + if(tokens.length == 0) + tokens = new String[]{query}; + for(String s : tokens) { + if(s.startsWith("-metric")) { + query = query.replace(s, ""); + String[] metricTokens = s.split(","); + CommandMetric metric = new CommandMetric(metricTokens[1], metricTokens[2]); + + String matchFilter = null; + try { + matchFilter = metricTokens[3]; + if(matchFilter.replaceAll(" ", "").equals("")) matchFilter = null; + } catch(Exception e) { + Log.d("RBS", "METRIC FILTER NULL."); + e.printStackTrace(); + } + + for(RTeam team : teams) { + boolean passesFilter = false; + boolean passesMatch = matchFilter == null; + StringBuilder matches = new StringBuilder(); + for(RTab tab : team.getTabs()) { + if(matchFilter != null && tab.getSimpleMatchString(matchFilter)) { + passesMatch = true; + } + + for(RMetric m : tab.getMetrics()) { + if(metric.metricPassesCriteria(m)) { + passesFilter = true; + matches.append(tab.getTitle()).append((", ")); + } + } + } + if(passesFilter && passesMatch) { + team.setFilterTag(team.getFilterTag()+"\nPasses "+metric.getMetricName()+" filter in match(es) "+matches.toString()+"\n"); + team.setCustomRelevance(1); + metricFiltered = true; + } + } + } + } + } catch(Exception e) { + Log.d("RBS", "Failed: "+e.getMessage()); + e.printStackTrace(); + } + + Log.d("RBS", "Proceeding with query: "+query); + + for(RTeam team : teams) { // assign search relevance to the team - processTeamForSearch(team); + processTeamForSearch(team, metricFiltered); } try { @@ -257,7 +314,7 @@ else if(filter == TeamsView.SORT_TYPE.CUSTOM_SORT) { /* * Finally, check to see if the user also wants to sort through the array */ - for(RTeam team : teams) processTeamForSearch(team); + for(RTeam team : teams) processTeamForSearch(team, false); try { Collections.sort(teams); @@ -279,7 +336,7 @@ else if(filter == TeamsView.SORT_TYPE.CUSTOM_SORT) { * This method will return automatically if the query is null * @param team the team to set relevance to based on query */ - private void processTeamForSearch(RTeam team) { + private void processTeamForSearch(RTeam team, boolean searchingAfterMetricFilter) { if(query == null || query.equals("")) return; int relevance = 0; @@ -316,11 +373,14 @@ private void processTeamForSearch(RTeam team) { } } - team.setCustomRelevance(team.getCustomRelevance() + relevance); + if(!searchingAfterMetricFilter || team.getCustomRelevance() != 0) team.setCustomRelevance(team.getCustomRelevance() + relevance); } public void quit() { interrupt(); + try { + super.join(); + } catch(Exception e) {} } } diff --git a/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsRecyclerAdapter.java b/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsRecyclerAdapter.java index 2e48ed3..6eccc12 100644 --- a/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsRecyclerAdapter.java +++ b/app/src/main/java/com/cpjd/roblu/ui/teams/TeamsRecyclerAdapter.java @@ -105,7 +105,7 @@ public void setTeams(ArrayList teams, boolean hideZeroRelevanceTeams) { if(hideZeroRelevanceTeams) { this.teams = new ArrayList<>(teams); // clones the array for(int i = 0; i < this.teams.size(); i++) { - if(this.teams.get(i).getCustomRelevance() == 0) { + if(this.teams.get(i).getCustomRelevance() <= 0) { this.teams.remove(i); i--; } 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 b450599..ef0e0c4 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 @@ -26,9 +26,13 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RadioGroup; +import android.widget.Spinner; import android.widget.TextView; import com.cpjd.main.TBA; @@ -39,10 +43,18 @@ import com.cpjd.roblu.models.RForm; import com.cpjd.roblu.models.RSettings; import com.cpjd.roblu.models.RTeam; +import com.cpjd.roblu.models.metrics.RBoolean; +import com.cpjd.roblu.models.metrics.RCalculation; +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.RMetric; +import com.cpjd.roblu.models.metrics.RSlider; +import com.cpjd.roblu.models.metrics.RStopwatch; +import com.cpjd.roblu.models.metrics.RTextfield; import com.cpjd.roblu.sync.cloud.Service; import com.cpjd.roblu.ui.UIHandler; import com.cpjd.roblu.ui.events.EventDrawerManager; -import com.cpjd.roblu.ui.mymatches.MyMatches; import com.cpjd.roblu.ui.setup.SetupActivity; import com.cpjd.roblu.ui.team.TeamViewer; import com.cpjd.roblu.ui.teamsSorting.CustomSort; @@ -248,7 +260,8 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { // If the user closes the search bar, refresh the teams view with all the original items searchView.setOnSearchViewListener(new MaterialSearchView.SearchViewListener() { @Override - public void onSearchViewShown() {} + public void onSearchViewShown() { + } @Override public void onSearchViewClosed() { executeLoadTeamsTask(lastFilter, false); @@ -272,7 +285,6 @@ public boolean onQueryTextChange(String newText) { return true; } }); - // Make general changes to UI, keep it synced with RUI new UIHandler(this, toolbar, searchButton, true).update(); @@ -304,7 +316,7 @@ public boolean onQueryTextChange(String newText) { settings.setUpdateLevel(Constants.VERSION); AlertDialog.Builder builder = new AlertDialog.Builder(TeamsView.this) - .setTitle("Changelist for Version 4.5.7") + .setTitle("Changelist for Version 4.5.8") .setMessage(Constants.UPDATE_MESSAGE) .setPositiveButton("Rock on", new DialogInterface.OnClickListener() { @Override @@ -593,10 +605,59 @@ public void run() { @Override public boolean onLongClick(View v) { if(v.getId() == R.id.fab) { - if(eventDrawerManager.getEvent() == null) return false; - Intent intent = new Intent(this, MyMatches.class); - intent.putExtra("eventID", eventDrawerManager.getEvent().getID()); - startActivity(intent); + final Dialog d = new Dialog(this); + d.setTitle("Add metric filter:"); + d.setContentView(R.layout.metric_chooser_filter); + final Spinner spinner = d.findViewById(R.id.type); + String[] values; + RForm form = io.loadForm(eventDrawerManager.getEvent().getID()); + final ArrayList metrics = new ArrayList<>(form.getPit()); + metrics.addAll(form.getMatch()); + + // Remove all but counters, stopwatches, and sliders + for(int i = 0; i < metrics.size(); i++) { + if(!(metrics.get(i) instanceof RCounter) && !(metrics.get(i) instanceof RStopwatch && !(metrics.get(i) instanceof RSlider)) && !(metrics.get(i) instanceof RCalculation) + && !(metrics.get(i) instanceof RBoolean) && !(metrics.get(i) instanceof RChooser) && !(metrics.get(i) instanceof RCheckbox) + && !(metrics.get(i) instanceof RTextfield)) { + metrics.remove(i); + i--; + } + } + + values = new String[metrics.size()]; + for(int i = 0; i < metrics.size(); i++) { + values[i] = Utils.getMetricType(metrics.get(i)) +" "+metrics.get(i).getTitle(); + } + ArrayAdapter adp = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, values); + adp.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adp); + + final EditText editText = d.findViewById(R.id.constraint); + final EditText matchConstraint = d.findViewById(R.id.matchConstraint); + + Button button = d.findViewById(R.id.button7); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + if(loadTeamsTask != null) loadTeamsTask.quit(); + RMetric m = new ArrayList<>(metrics).get(spinner.getSelectedItemPosition()); + String yeet = (lastQuery == null ? "" : lastQuery) + " -metric,"+m.getTitle().replaceAll(" ", "")+","+editText.getText().toString() + + (matchConstraint.getText().toString() != null && !matchConstraint.getText().toString().equals("") ? ","+matchConstraint.getText().toString() : ""); + searchView.showSearch(false); + searchView.setQuery(yeet, false); + lastQuery = yeet; + } catch(Exception e) { + Log.d("RBS", "Failed to select metric"); + } finally { + d.dismiss(); + } + + } + }); + if(d.getWindow() != null) d.getWindow().getAttributes().windowAnimations = new IO(getApplicationContext()).loadSettings().getRui().getAnimation(); + d.show(); + return true; } return false; 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 2c83dc4..b7b0f14 100644 --- a/app/src/main/java/com/cpjd/roblu/utils/Constants.java +++ b/app/src/main/java/com/cpjd/roblu/utils/Constants.java @@ -13,9 +13,9 @@ public abstract class Constants { public static final String SERVICE_ID = "com.cpjd.roblu.service"; - public static final String UPDATE_MESSAGE = "-Added team picks functionality"; + public static final String UPDATE_MESSAGE = "-Added multi-filter option (long press search button)\n-IOS edition of Roblu Scouter coming soon!\n-Bug fixes"; - public static final int VERSION = 12; // used for updating the changelist + public static final int VERSION = 13; // used for updating the changelist /* * v4.0.0 cross activity codes 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 0e9b51c..845d373 100644 --- a/app/src/main/java/com/cpjd/roblu/utils/Utils.java +++ b/app/src/main/java/com/cpjd/roblu/utils/Utils.java @@ -35,10 +35,12 @@ 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.RCalculation; 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; import com.cpjd.roblu.models.metrics.RStopwatch; @@ -217,7 +219,7 @@ public static void showSnackbar(View layout, Context context, String text, boole /** * We can almost always determine the match key of a match based off - * it's name. + * its name. * @param matchName the name of the match * @return the guessed TBA key for this match */ @@ -647,5 +649,19 @@ private static String getSaltString() { return saltStr; } + + public static String getMetricType(RMetric metric) { + if(metric instanceof RCounter) return "[Counter]"; + else if(metric instanceof RSlider) return "[Slider]"; + else if(metric instanceof RStopwatch) return "[Stopwatch]"; + else if(metric instanceof RCalculation) return "[Calculation]"; + else if(metric instanceof RBoolean) return "[Boolean]"; + else if(metric instanceof RCheckbox) return "[Checkbox]"; + else if(metric instanceof RChooser) return "[Chooser]"; + else if(metric instanceof RTextfield) return "[Textfield]"; + else if(metric instanceof RFieldData) return "[FieldDiagram]"; + else if(metric instanceof RGallery) return "[Gallery]"; + return ""; + } } diff --git a/app/src/main/java/com/cpjd/roblu/utils/searchCommands/CommandMetric.java b/app/src/main/java/com/cpjd/roblu/utils/searchCommands/CommandMetric.java new file mode 100644 index 0000000..9e9801c --- /dev/null +++ b/app/src/main/java/com/cpjd/roblu/utils/searchCommands/CommandMetric.java @@ -0,0 +1,113 @@ +package com.cpjd.roblu.utils.searchCommands; + +import android.util.Log; + +import com.cpjd.roblu.models.metrics.RBoolean; +import com.cpjd.roblu.models.metrics.RCalculation; +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.RMetric; +import com.cpjd.roblu.models.metrics.RSlider; +import com.cpjd.roblu.models.metrics.RStopwatch; +import com.cpjd.roblu.models.metrics.RTextfield; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +import lombok.Data; + +@Data +public class CommandMetric { + + private String metricName; + private String metricFilter; + + public CommandMetric(String metricName, String metricFilter) { + this.metricName = metricName.replaceAll("\\s+", ""); + this.metricFilter = metricFilter.replaceAll("\\s+", ""); + + Log.d("RBS", "Loading command metric for metricName: "+metricName+" and metricFilter: "+metricFilter); + } + + public boolean metricPassesCriteria(RMetric metric) { + if(!metric.getTitle().replaceAll("\\s+", "").equals(metricName)) return false; + + if(metric instanceof RCalculation) return passesNumerical(metric); + else if(metric instanceof RCounter) return passesNumerical(metric); + else if(metric instanceof RSlider) return passesNumerical(metric); + else if(metric instanceof RStopwatch) return passesNumerical(metric); + else if(metric instanceof RBoolean) return passesBoolean(((RBoolean) metric).isValue()); + else if(metric instanceof RCheckbox) { + ArrayList b = new ArrayList<>(); + for(String s : ((RCheckbox) metric).getValues().keySet()) b.add(((RCheckbox) metric).getValues().get(s)); + return passesCheckbox(b); + } else if(metric instanceof RChooser) { + return ((RChooser) metric).getValues()[((RChooser) metric).getSelectedIndex()].equals(metricFilter); + } else if(metric instanceof RTextfield) { + return ((RTextfield) metric).getText().contains(metricFilter); + } + + return false; + } + + private boolean passesNumerical(RMetric metric) { + if(metric instanceof RCalculation || metric instanceof RCounter || metric instanceof RSlider || metric instanceof RStopwatch) { + Pattern greatOrEqual = Pattern.compile(">=\\d+"); + Pattern lessOrEqual = Pattern.compile("<=\\d+"); + Pattern great = Pattern.compile(">\\d+"); + Pattern less = Pattern.compile("<\\d+"); + Pattern equal = Pattern.compile("=\\d+"); + Pattern range = Pattern.compile("=\\d+-\\d+"); + + double value; + if(metric instanceof RCalculation) value = ((RCalculation) metric).getLastValue(); + else if(metric instanceof RCounter) value = ((RCounter) metric).getValue(); + else if(metric instanceof RSlider) value = ((RSlider) metric).getValue(); + else value = ((RStopwatch) metric).getTime(); + + try { + if(greatOrEqual.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.replace(">=", "")) >= value; + } + if(lessOrEqual.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.replace("<=", "")) <= value; + } + if(less.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.replace("<", "")) > value; + } + if(great.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.replace(">", "")) > value; + } + if(equal.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.replace("=", "")) == value; + } + if(range.matcher(metricFilter).matches()) { + return Double.parseDouble(metricFilter.split("-")[0]) <= value && value <= Double.parseDouble(metricFilter.split("-")[1]); + } + } catch(Exception e) { + // + } + } + + return false; + } + + private boolean passesBoolean(boolean value) { + return value == metricFilter.toLowerCase().contains("t"); + } + + private boolean passesCheckbox(ArrayList values) { + try { + String[] split = metricFilter.split(":"); + for(int i = 0; i < split.length; i++) { + split[i] = split[i].toLowerCase().replaceAll("t", "true").replaceAll("f", "false"); + if(split[i].equals("true") != values.get(i)) return false; + } + return true; + } catch(Exception e) { + // + } + return false; + } +} diff --git a/app/src/main/res/layout/metric_chooser_filter.xml b/app/src/main/res/layout/metric_chooser_filter.xml new file mode 100644 index 0000000..4665efb --- /dev/null +++ b/app/src/main/res/layout/metric_chooser_filter.xml @@ -0,0 +1,47 @@ + + + + + + +