From 793ba1a75d35ca8b15b8c138d944b1d6e0728688 Mon Sep 17 00:00:00 2001 From: Astatin3 <77305074+Astatin3@users.noreply.github.com> Date: Tue, 2 Jul 2024 21:48:01 -0600 Subject: [PATCH] Work on search menu, add "compiled" view mode --- README.md | 15 +- app/build.gradle.kts | 1 + .../SettingsVersionStack/sv1.java | 9 ++ .../scoutingData/ScoutingVersion.java | 139 ++++++++++++++++++ .../scoutingapp2025/scoutingData/fields.java | 6 +- .../scoutingapp2025/ui/data/searchView.java | 104 ++++++++++--- 6 files changed, 240 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 08c68d2..52ce15d 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,18 @@ Ridgebotics 2025 scouting app in Android TODO: +- Make the "Search" menu +- Add the rest of the "Compiled" mode graphs +- Make pit and match data field builder UIs. I don't want to have to keep editing a variable + +- Add more types of data fields. +- Make the "Compile" menu - Make the file browser UI - Add white border around the datamatrix code to allow file transfer in dark mode - Fix the code scanning progress indicator -- Make pit and match data field builder UIs. I don't want to have to keep editing a variable -Also TODO: -- Add more types of data fields. -- Make the "Search" menu -- Make the "Compile" menu - -Also Also TODO: -- Make practice mode - Make server software to allow for easy sync over wifi +- Make practice mode - AI overview of scouting data for a team??? - Bluetooth data sync - Test the scouting app \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0a3a7fd..339a78b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,4 +61,5 @@ dependencies { implementation("com.journeyapps:zxing-android-embedded:4.3.0") implementation("com.github.skydoves:powerspinner:1.2.7") + implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") } \ No newline at end of file diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/SettingsVersionStack/sv1.java b/app/src/main/java/com/astatin3/scoutingapp2025/SettingsVersionStack/sv1.java index 470c051..5728cfe 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/SettingsVersionStack/sv1.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/SettingsVersionStack/sv1.java @@ -26,6 +26,7 @@ public class sv1 extends sv0 { writeTag("match_num", "0"); writeTag("alliance_pos", "red-1"); + writeTag("compiled_mode", "false"); } public int get_match_num(){ @@ -61,4 +62,12 @@ public class sv1 extends sv0 { set_team_num(Integer.parseInt(str)); } public void set_team_num(int num){writeTag("team_num", String.valueOf(num));} + + public boolean get_compiled_mode(){ + return readTag("compiled_mode").equals("true"); + } + + public void set_compiled_mode(boolean state){ + writeTag("compiled_mode", state ? "true" : "false"); + } } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingVersion.java b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingVersion.java index 45fefb8..5a5b10f 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingVersion.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingVersion.java @@ -2,6 +2,7 @@ package com.astatin3.scoutingapp2025.scoutingData; import android.app.slice.Slice; import android.content.Context; +import android.graphics.Color; import android.text.Editable; import android.text.TextWatcher; import android.view.Gravity; @@ -18,6 +19,11 @@ import androidx.annotation.Nullable; import com.astatin3.scoutingapp2025.SettingsVersionStack.latestSettings; import com.astatin3.scoutingapp2025.utility.BuiltByteParser; import com.astatin3.scoutingapp2025.utility.ByteBuilder; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; import com.google.android.material.slider.Slider; import com.skydoves.powerspinner.IconSpinnerAdapter; import com.skydoves.powerspinner.IconSpinnerItem; @@ -74,6 +80,7 @@ ScoutingVersion { public Object default_value; public abstract inputTypes getInputType(); public abstract valueTypes getValueType(); + public abstract Object get_fallback_value(); public abstract int get_byte_id(); public inputType(){} public inputType(String name){ @@ -87,6 +94,8 @@ ScoutingVersion { public abstract void setViewValue(Object value); public abstract dataType getViewValue(); public abstract void add_individual_view(LinearLayout parent, dataType data); + public abstract void add_compiled_view(LinearLayout parent, dataType[] data); + } @@ -103,6 +112,7 @@ ScoutingVersion { public int get_byte_id() {return slider_type_id;} public inputTypes getInputType(){return inputTypes.SLIDER;} public valueTypes getValueType(){return valueTypes.NUM;} + public Object get_fallback_value(){return 0;} public sliderType(){}; public sliderType(String name, int defaultValue, int min, int max){ super(name); @@ -166,6 +176,111 @@ ScoutingVersion { slider.setEnabled(false); parent.addView(slider); } + + + private float calculateMean(int[] data) { + float sum = 0; + for (int value : data) { + sum += (float) value; + } + return sum / data.length; + } + + private float calculateStandardDeviation(int[] data, float mean) { + float sum = 0; + for (int value : data) { + sum += Math.pow((float) value - mean, 2); + } + return (float) Math.sqrt(sum / (data.length - 1)); + } + + private List generateNormalDistribution(float mean, float stdDev, int count, int scale) { + List entries = new ArrayList<>(); + for (int i = 0; i < count; i++) { + float x = i; + float y = (float) ((1 / (stdDev * Math.sqrt(2 * Math.PI))) + * Math.exp(-0.5 * Math.pow((x - mean) / stdDev, 2))); + entries.add(new Entry(x, y*scale)); // Scale y for visibility + } + return entries; + } + + + + public void add_compiled_view(LinearLayout parent, dataType[] data){ + LineChart chart = new LineChart(parent.getContext()); + FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + layout.height = 350; + chart.setLayoutParams(layout); + chart.setBackgroundColor(0xff252025); + + int[] values = new int[max-min]; + for (int i = 0; i < data.length; i++) + values[(int) data[i].get()-min-1]++; + + + int[] temp = new int[data.length]; + for (int i = 0; i < data.length; i++) + temp[i] = (int) data[i].get(); + + + + List entries = new ArrayList<>(); + for (int i = 0; i < values.length; i++) + entries.add(new Entry(i, values[i])); + + + LineDataSet dataSet = new LineDataSet(entries, name); + dataSet.setColor(Color.BLUE); + dataSet.setValueTextColor(Color.BLACK); + dataSet.setDrawCircles(false); + dataSet.setDrawValues(false); + + + + // Calculate mean and standard deviation + float mean = calculateMean(temp); + float stdDev = calculateStandardDeviation(temp, mean); + + // Generate normal distribution curve + List normalDistEntries = generateNormalDistribution(mean-min, stdDev, max-min, (max-min)/data.length); + + + LineDataSet normalDistSet = new LineDataSet(normalDistEntries, "Normal Distribution"); + normalDistSet.setColor(Color.RED); + normalDistSet.setDrawCircles(false); + normalDistSet.setDrawValues(false); + normalDistSet.setLineWidth(2f); + + + + + LineData lineData = new LineData(dataSet, normalDistSet); + + + + chart.setData(lineData); + chart.invalidate(); + + chart.getDescription().setEnabled(false); + chart.setTouchEnabled(false); + chart.setDragEnabled(false); + chart.setScaleEnabled(false); + + dataSet.setValueTextColor(Color.RED); + + chart.getXAxis().setTextColor(Color.BLUE); + chart.getAxisLeft().setTextColor(Color.GREEN); + chart.getAxisRight().setTextColor(Color.GREEN); + + Legend legend = chart.getLegend(); + legend.setTextColor(Color.MAGENTA); + + parent.addView(chart); + } } @@ -185,6 +300,7 @@ ScoutingVersion { public int get_byte_id() {return dropdownType;} public inputTypes getInputType(){return inputTypes.DROPDOWN;} public valueTypes getValueType(){return valueTypes.NUM;} + public Object get_fallback_value(){return 0;} public dropdownType(){}; public dropdownType(String name, String[] text_options, int defaultSelIndex){ super(name); @@ -269,6 +385,17 @@ ScoutingVersion { tv.setTextSize(18); parent.addView(tv); } + public void add_compiled_view(LinearLayout parent, dataType[] data){ + TextView tv = new TextView(parent.getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText(""); + tv.setTextSize(20); + parent.addView(tv); + } } @@ -285,6 +412,7 @@ ScoutingVersion { public int get_byte_id() {return notesType;} public inputTypes getInputType(){return inputTypes.NOTES_INPUT;} public valueTypes getValueType(){return valueTypes.STRING;} + public Object get_fallback_value(){return "";} public notesType(){}; public notesType(String name, String default_text){ super(name); @@ -338,6 +466,17 @@ ScoutingVersion { tv.setTextSize(18); parent.addView(tv); } + public void add_compiled_view(LinearLayout parent, dataType[] data){ + TextView tv = new TextView(parent.getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText(""); + tv.setTextSize(20); + parent.addView(tv); + } } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java index 6dfc3b8..ba215aa 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java @@ -14,14 +14,14 @@ public class fields { public static final ScoutingVersion.inputType[][] default_match_fields = new ScoutingVersion.inputType[][] { { - sv.new sliderType("How good is robot", 5, 0, 10), + sv.new sliderType("How good is robot", 5, 1, 10), sv.new notesType("notes", ""), },{ - sv.new sliderType("How good is robot", 5, 0, 10), + sv.new sliderType("How good is robot", 5, 1, 10), sv.new sliderType("Test", 128, 64, 256), sv.new notesType("notes", ""), },{ - sv.new sliderType("How good is robot", 5, 0, 10), + sv.new sliderType("How good is robot", 5, 1, 10), sv.new sliderType("Test", 128, 64, 256), sv.new dropdownType("test-dropdown", new String[]{"Test1", "test2", "Three"}, 1), sv.new notesType("notes", ""), diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java index 3901f8f..678b0c0 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java @@ -4,6 +4,8 @@ import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TableLayout; @@ -13,6 +15,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.constraintlayout.widget.ConstraintLayout; +import com.astatin3.scoutingapp2025.SettingsVersionStack.latestSettings; import com.astatin3.scoutingapp2025.databinding.FragmentDataBinding; import com.astatin3.scoutingapp2025.scoutingData.ScoutingDataWriter; import com.astatin3.scoutingapp2025.scoutingData.ScoutingVersion; @@ -37,8 +40,10 @@ public class searchView extends ConstraintLayout { frcEvent event; ScoutingVersion.inputType[][] match_values; + ScoutingVersion.inputType[] latest_match_values; ScoutingVersion.transferType[][] match_transferValues; ScoutingVersion.inputType[][] pit_values; + ScoutingVersion.inputType[] latest_pit_values; ScoutingVersion.transferType[][] pit_transferValues; public void init(FragmentDataBinding binding, frcEvent event){ @@ -48,8 +53,10 @@ public class searchView extends ConstraintLayout { match_values = fields.load(fields.matchFieldsFilename); + latest_match_values = match_values[match_values.length-1]; match_transferValues = fields.sv.get_transfer_values(match_values); pit_values = fields.load(fields.pitsFieldsFilename); + latest_pit_values = pit_values[pit_values.length-1]; pit_transferValues = fields.sv.get_transfer_values(pit_values); @@ -102,12 +109,12 @@ public class searchView extends ConstraintLayout { frcTeam finalTeam = team; tr.setOnClickListener(v -> { - loadTeam(finalTeam); + loadTeam(finalTeam, latestSettings.settings.get_compiled_mode()); }); } } - public void loadTeam(frcTeam team){ + public void loadTeam(frcTeam team, boolean compiled_mode){ binding.searchArea.removeAllViews(); LinearLayout ll = new LinearLayout(getContext()); @@ -118,6 +125,22 @@ public class searchView extends ConstraintLayout { ll.setOrientation(LinearLayout.VERTICAL); binding.searchArea.addView(ll); + CheckBox cb = new CheckBox(getContext()); + cb.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + cb.setText("Compiled mode"); + cb.setChecked(compiled_mode); + ll.addView(cb); + cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + latestSettings.settings.set_compiled_mode(isChecked); + loadTeam(team, isChecked); + } + }); + TextView tv = new TextView(getContext()); tv.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -138,47 +161,82 @@ public class searchView extends ConstraintLayout { tv.setTextSize(16); ll.addView(tv); + String[] files = fileEditor.getMatchesByTeamNum(team.teamNumber); - for(int i = 0; i < files.length; i++){ - String[] split = files[i].split("-"); - int match_num = Integer.parseInt(split[1]); - - ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[i], match_values, match_transferValues); - + if(files.length == 0){ tv = new TextView(getContext()); tv.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT )); - tv.setPadding(0,20,0,5); tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Match " + (match_num) + " by " + psda.username); - tv.setTextSize(30); + tv.setText("No match data has been collected!"); + tv.setTextSize(23); ll.addView(tv); + return; + } + + if(!compiled_mode){ + + for (int i = 0; i < files.length; i++) { + String[] split = files[i].split("-"); + int match_num = Integer.parseInt(split[1]); + + ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[i], match_values, match_transferValues); - for(int a = 0 ; a < psda.data.array.length; a++){ tv = new TextView(getContext()); tv.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT )); + tv.setPadding(0, 20, 0, 5); tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(psda.data.array[a].name); - tv.setTextSize(25); + tv.setText("Match " + (match_num) + " by " + psda.username); + tv.setTextSize(30); ll.addView(tv); + for (int a = 0; a < psda.data.array.length; a++) { + tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText(psda.data.array[a].name); + tv.setTextSize(25); + ll.addView(tv); - match_values[match_values.length-1][a].add_individual_view(ll, psda.data.array[a]); + + latest_match_values[a].add_individual_view(ll, psda.data.array[a]); + } + } + + + } else { + ScoutingVersion.dataType[][] data = new ScoutingVersion.dataType[latest_match_values.length][files.length]; + for (int i = 0; i < files.length; i++) { + + ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[i], match_values, match_transferValues); + for (int a = 0; a < data.length; a++) { + data[a][i] = psda.data.array[a]; + } + } + + for(int i = 0; i < latest_match_values.length; i++){ + tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setPadding(0, 20, 0, 5); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText(latest_match_values[i].name); + tv.setTextSize(30); + ll.addView(tv); + + latest_match_values[i].add_compiled_view(ll, data[i]); } } - - System.out.println(ll.getChildCount()); - -// ScoutingDataWriter.ParsedScoutingDataResult[] scoutingData; - } - - public void display_uncompiled_data(){ - } }