diff --git a/README.md b/README.md index 52ce15d..cfb18ef 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,13 @@ # ScoutingApp2025 - Ridgebotics 2025 scouting app in Android +1 Ridgebotics 2025 scouting app in Android TODO: -- Make the "Search" menu -- Add the rest of the "Compiled" mode graphs +- Make a word cloud for the compiled mode of - 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 server software to allow for easy sync over wifi diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/MainActivity.java b/app/src/main/java/com/astatin3/scoutingapp2025/MainActivity.java index 1c893d1..8f27c7a 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/MainActivity.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/MainActivity.java @@ -17,6 +17,8 @@ import com.astatin3.scoutingapp2025.databinding.ActivityMainBinding; import com.astatin3.scoutingapp2025.SettingsVersionStack.latestSettings; +import java.util.Objects; + public class MainActivity extends AppCompatActivity { @@ -36,6 +38,8 @@ public class MainActivity extends AppCompatActivity { fields.save(fields.pitsFieldsFilename, fields.default_pit_fields); } + Objects.requireNonNull(getSupportActionBar()).hide(); + super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingDataWriter.java b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingDataWriter.java index a9cf3b8..ea6ecef 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingDataWriter.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/ScoutingDataWriter.java @@ -55,7 +55,7 @@ public class ScoutingDataWriter { dataType[] dataTypes = new dataType[objects.size()-2]; int version = ((int)objects.get(0).get()); - System.out.println(version); +// System.out.println(version); String username = (String) objects.get(1).get(); for(int i = 0; i < values[version].length; i++){ 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 e3b28b2..0c87682 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java @@ -25,7 +25,7 @@ public class fields { new sliderType("Test", 128, 64, 256), new notesType("notes", ""), },{ - new sliderType("How good is robot", 5, 1, 10), + new sliderType("How good is robot", 5, 5, 10), new sliderType("Test", 128, 64, 256), new dropdownType("test-dropdown", new String[]{"Test1", "test2", "Three"}, 1), new notesType("notes", ""), @@ -70,7 +70,7 @@ public class fields { public static inputType[][] load(String filename){ byte[] bytes = fileEditor.readFile(filename); - System.out.println(bytes); +// System.out.println(bytes); try { BuiltByteParser bbp = new BuiltByteParser(bytes); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/dropdownType.java b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/dropdownType.java index fae6f72..ec2ded5 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/dropdownType.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/dropdownType.java @@ -1,6 +1,7 @@ package com.astatin3.scoutingapp2025.types.input; import android.content.Context; +import android.graphics.Color; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -10,10 +11,17 @@ import android.widget.TextView; import androidx.annotation.Nullable; +import com.astatin3.scoutingapp2025.R; import com.astatin3.scoutingapp2025.types.data.dataType; import com.astatin3.scoutingapp2025.types.data.intType; import com.astatin3.scoutingapp2025.utility.BuiltByteParser; import com.astatin3.scoutingapp2025.utility.ByteBuilder; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; import com.skydoves.powerspinner.IconSpinnerAdapter; import com.skydoves.powerspinner.IconSpinnerItem; import com.skydoves.powerspinner.OnSpinnerItemSelectedListener; @@ -69,6 +77,7 @@ public class dropdownType extends inputType { dropdown.setPadding(10,10,10,10); dropdown.setBackgroundColor(0xf0000000); + dropdown.setTextColor(0xff00ff00); dropdown.setTextSize(15); dropdown.setArrowGravity(SpinnerGravity.END); dropdown.setArrowPadding(8); @@ -114,16 +123,47 @@ public class dropdownType extends inputType { tv.setTextSize(18); parent.addView(tv); } + private static int[] generateEquidistantColors(int N) { + int[] colors = new int[N]; + float[] hsv = new float[3]; // Hue, Saturation, Value + + for (int i = 0; i < N; i++) { + float hue = i * 1.0F / N; + hsv[0] = hue * 360; // Convert hue to degrees (0 to 360) + hsv[1] = 1; // Maximum saturation + hsv[2] = 1; // Maximum brightness (value) + + colors[i] = Color.HSVToColor(hsv); + } + return colors; + } + public void add_compiled_view(LinearLayout parent, dataType[] data){ - TextView tv = new TextView(parent.getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( + PieChart chart = new PieChart(parent.getContext()); + FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(""); - tv.setTextSize(20); - parent.addView(tv); + ); + layout.height = 350; + chart.setLayoutParams(layout); + chart.setBackgroundColor(0xff252025); + parent.addView(chart); + + int[] data_2 = new int[text_options.length]; + for(int i = 0; i < data.length; i++) + data_2[(int) data[i].get()]++; + + List entries = new ArrayList<>(); + for(int i = 0; i < data_2.length; i++) { + PieEntry entry = new PieEntry((float) data_2[i], text_options[i]); + entries.add(entry); + } + + PieDataSet pieDataSet = new PieDataSet(entries, name); + pieDataSet.setColors(generateEquidistantColors(text_options.length)); + PieData pieData = new PieData(pieDataSet); + chart.setDrawHoleEnabled(false); + chart.setData(pieData); } } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/sliderType.java b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/sliderType.java index 68dce37..32cece8 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/sliderType.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/sliderType.java @@ -62,7 +62,7 @@ public class sliderType extends inputType { public View createView(Context context, Function onUpdate){ slider = new Slider(context); setViewValue(default_value); - slider.setStepSize((float) 1 / max); + slider.setStepSize((float) 1 / (max-min)); slider.addOnChangeListener(new Slider.OnChangeListener() { @Override public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) { @@ -75,7 +75,7 @@ public class sliderType extends inputType { public void setViewValue(Object value) { if(slider == null) return; float slider_position = (float) ((int) value-min) / (max-min); - float step_size = (float) 1/max; + float step_size = (float) 1/(max-min); int round_position = Math.round(slider_position / step_size); slider.setValue(round_position*step_size); } @@ -136,9 +136,9 @@ public class sliderType extends inputType { chart.setLayoutParams(layout); chart.setBackgroundColor(0xff252025); - int[] values = new int[max-min]; + int[] values = new int[max-min+1]; for (int i = 0; i < data.length; i++) - values[(int) data[i].get()-min-1]++; + values[(int) data[i].get()-min]++; int[] temp = new int[data.length]; @@ -165,7 +165,7 @@ public class sliderType extends inputType { float stdDev = calculateStandardDeviation(temp, mean); // Generate normal distribution curve - List normalDistEntries = generateNormalDistribution(mean-min, stdDev, max-min, (max-min)/data.length); + List normalDistEntries = generateNormalDistribution(mean-min, stdDev, max-min+1, (max-min)/data.length); LineDataSet normalDistSet = new LineDataSet(normalDistEntries, "Normal Distribution"); @@ -191,12 +191,12 @@ public class sliderType extends inputType { dataSet.setValueTextColor(Color.RED); - chart.getXAxis().setTextColor(Color.BLUE); - chart.getAxisLeft().setTextColor(Color.GREEN); - chart.getAxisRight().setTextColor(Color.GREEN); + chart.getXAxis().setTextColor(Color.WHITE); + chart.getAxisLeft().setTextColor(Color.WHITE); + chart.getAxisRight().setTextColor(Color.WHITE); Legend legend = chart.getLegend(); - legend.setTextColor(Color.MAGENTA); + legend.setTextColor(Color.WHITE); parent.addView(chart); } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/dataFragment.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/dataFragment.java index 550f796..4c57079 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/dataFragment.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/dataFragment.java @@ -42,7 +42,7 @@ public class dataFragment extends Fragment { frcEvent event = frcEvent.decode(fileEditor.readFile(evcode + ".eventdata")); - binding.overviewButton.setOnClickListener(v -> { + binding.overviewView.setOnClickListener(v -> { binding.buttons.setVisibility(View.GONE); binding.overviewView.setVisibility(View.VISIBLE); binding.overviewView.start(binding, event); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/overviewView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/statusView.java similarity index 90% rename from app/src/main/java/com/astatin3/scoutingapp2025/ui/data/overviewView.java rename to app/src/main/java/com/astatin3/scoutingapp2025/ui/data/statusView.java index 2a3296e..172a5e1 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/overviewView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/statusView.java @@ -15,17 +15,18 @@ import com.astatin3.scoutingapp2025.databinding.FragmentDataBinding; import com.astatin3.scoutingapp2025.utility.fileEditor; import com.astatin3.scoutingapp2025.types.frcEvent; import com.astatin3.scoutingapp2025.types.frcMatch; -import com.astatin3.scoutingapp2025.types.frcTeam; import java.util.Arrays; -public class overviewView extends ScrollView { - public overviewView(@NonNull Context context) { +public class statusView extends ScrollView { + public statusView(@NonNull Context context) { super(context); } - public overviewView(Context context, AttributeSet attributeSet){ + public statusView(Context context, AttributeSet attributeSet){ super(context, attributeSet); } + public static int color_found = 0x7f00ff00; + public static int color_not_found = 0x7f7f0000; private void addTableText(TableRow tr, String textStr){ TextView text = new TextView(getContext()); text.setTextSize(18); @@ -34,6 +35,7 @@ public class overviewView extends ScrollView { tr.addView(text); } public void start(FragmentDataBinding binding, frcEvent event) { + binding.matchTable.removeAllViews(); binding.matchTable.setStretchAllColumns(true); add_pit_scouting(binding, event); add_match_scouting(binding, event); @@ -75,9 +77,9 @@ public class overviewView extends ScrollView { text.setText(String.valueOf(num)); if(fileEditor.fileExist(event.eventCode + "-" + num + ".pitscoutdata")){ - text.setBackgroundColor(0x3000FF00); + text.setBackgroundColor(color_found); }else{ - text.setBackgroundColor(0x30FF0000); + text.setBackgroundColor(color_not_found); } tr.addView(text); } @@ -108,7 +110,6 @@ public class overviewView extends ScrollView { addTableText(tr, "Blue-3"); binding.matchTable.addView(tr); - boolean toggle = false; for(frcMatch match : event.matches){ tr = new TableRow(getContext()); @@ -132,9 +133,9 @@ public class overviewView extends ScrollView { text.setText(String.valueOf(team_num)); if(fileEditor.fileExist(event.eventCode + "-" + match.matchIndex + "-" + alliance_position + "-" + team_num + ".matchscoutdata")){ - text.setBackgroundColor(0x3000FF00); + text.setBackgroundColor(color_found); }else{ - text.setBackgroundColor(0x30FF0000); + text.setBackgroundColor(color_not_found); } tr.addView(text); } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/teamsView.java similarity index 67% rename from app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java rename to app/src/main/java/com/astatin3/scoutingapp2025/ui/data/teamsView.java index 4876a14..df4fe33 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/searchView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/data/teamsView.java @@ -16,7 +16,6 @@ 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.fields; import com.astatin3.scoutingapp2025.scoutingData.transfer.transferType; @@ -25,18 +24,19 @@ import com.astatin3.scoutingapp2025.types.frcEvent; import com.astatin3.scoutingapp2025.types.frcTeam; import com.astatin3.scoutingapp2025.types.input.inputType; import com.astatin3.scoutingapp2025.utility.fileEditor; +import com.google.android.material.divider.MaterialDivider; import java.util.Arrays; -public class searchView extends ConstraintLayout { - public searchView(@NonNull Context context) { +public class teamsView extends ConstraintLayout { + public teamsView(@NonNull Context context) { super(context); } - public searchView(Context context, AttributeSet attributeSet){ + public teamsView(Context context, AttributeSet attributeSet){ super(context, attributeSet); } - FragmentDataBinding binding; + com.astatin3.scoutingapp2025.databinding.FragmentDataBinding binding; String evcode; frcEvent event; @@ -47,7 +47,7 @@ public class searchView extends ConstraintLayout { inputType[] latest_pit_values; transferType[][] pit_transferValues; - public void init(FragmentDataBinding binding, frcEvent event){ + public void init(com.astatin3.scoutingapp2025.databinding.FragmentDataBinding binding, frcEvent event){ this.binding = binding; this.evcode = event.eventCode; this.event = event; @@ -115,7 +115,7 @@ public class searchView extends ConstraintLayout { } } - public void loadTeam(frcTeam team, boolean compiled_mode){ + public void loadTeam(frcTeam team, boolean compiled_mode) { binding.searchArea.removeAllViews(); LinearLayout ll = new LinearLayout(getContext()); @@ -148,6 +148,16 @@ public class searchView extends ConstraintLayout { ViewGroup.LayoutParams.WRAP_CONTENT )); tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText(String.valueOf(team.teamNumber)); + tv.setTextSize(28); + ll.addView(tv); + + tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); tv.setText(team.teamName); tv.setTextSize(28); ll.addView(tv); @@ -162,8 +172,87 @@ public class searchView extends ConstraintLayout { tv.setTextSize(16); ll.addView(tv); + add_pit_data(ll, team); + add_match_data(ll, team, compiled_mode); + } - String[] files = fileEditor.getMatchesByTeamNum(team.teamNumber); + public void add_pit_data(LinearLayout ll, frcTeam team){ + final String filename = evcode+"-"+team.teamNumber+".pitscoutdata"; + + ll.addView(new MaterialDivider(getContext())); + + TextView tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setPadding(0,10,0,10); + tv.setText("----- Pit data -----"); + tv.setTextSize(30); + ll.addView(tv); + + ll.addView(new MaterialDivider(getContext())); + + if(!fileEditor.fileExist(filename)){ + tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText("No pit data has been collected!"); + tv.setTextSize(23); + ll.addView(tv); + return; + } + + ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filename, pit_values, pit_transferValues); + + 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("Pit scouting 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); + + + latest_pit_values[a].add_individual_view(ll, psda.data.array[a]); + } + } + + public void add_match_data(LinearLayout ll, frcTeam team, boolean compiled_mode){ + String[] files = fileEditor.getMatchesByTeamNum(evcode, team.teamNumber); + + ll.addView(new MaterialDivider(getContext())); + + TextView tv = new TextView(getContext()); + tv.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + )); + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setText("----- Match data -----"); + tv.setPadding(0,10,0,10); + tv.setTextSize(30); + ll.addView(tv); + + ll.addView(new MaterialDivider(getContext())); if(files.length == 0){ tv = new TextView(getContext()); @@ -179,7 +268,6 @@ public class searchView extends ConstraintLayout { } if(!compiled_mode){ - for (int i = 0; i < files.length; i++) { String[] split = files[i].split("-"); int match_num = Integer.parseInt(split[1]); @@ -191,9 +279,9 @@ public class searchView extends ConstraintLayout { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT )); - tv.setPadding(0, 20, 0, 5); + tv.setPadding(0, 40, 0, 5); tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Match " + (match_num) + " by " + psda.username); + tv.setText("M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username); tv.setTextSize(30); ll.addView(tv); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/matchScoutingView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/matchScoutingView.java index eb533f1..a14982e 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/matchScoutingView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/matchScoutingView.java @@ -294,6 +294,9 @@ public class matchScoutingView extends ConstraintLayout { types[i] = latest_values[i].getViewValue(); } - System.out.println(ScoutingDataWriter.save(values.length-1, username, filename, types)); + if(ScoutingDataWriter.save(values.length-1, username, filename, types)) + System.out.println("Saved!"); + else + System.out.println("Error saving"); } } diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/pitScoutingView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/pitScoutingView.java index 6c3cf8d..e859e04 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/pitScoutingView.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/pitScoutingView.java @@ -56,7 +56,6 @@ public class pitScoutingView extends ConstraintLayout { ArrayList dataTypes; public void save(){ - System.out.println("Saved!"); edited = false; set_indicator_color(saved_color); @@ -66,7 +65,10 @@ public class pitScoutingView extends ConstraintLayout { types[i] = latest_values[i].getViewValue(); } - System.out.println(ScoutingDataWriter.save(values.length-1, username, filename, types)); + if(ScoutingDataWriter.save(values.length-1, username, filename, types)) + System.out.println("Saved!"); + else + System.out.println("Error saving"); } public void set_indicator_color(int color){ diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/utility/fileEditor.java b/app/src/main/java/com/astatin3/scoutingapp2025/utility/fileEditor.java index e86fbc6..1cc13f5 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/utility/fileEditor.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/utility/fileEditor.java @@ -219,7 +219,7 @@ public final class fileEditor { } - public static String[] getMatchesByTeamNum(int teamNum){ + public static String[] getMatchesByTeamNum(String evcode, int teamNum){ File f = new File(baseDir); File[] files = f.listFiles(); @@ -228,7 +228,8 @@ public final class fileEditor { if(files == null){return new String[0];} for (File file : files) { - if(!file.isDirectory() && file.getName().endsWith(teamNum+".matchscoutdata")) { + String name = file.getName(); + if(!file.isDirectory() && name.startsWith(evcode+"-") && name.endsWith("-"+teamNum+".matchscoutdata")) { outFiles.add(file.getName()); } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 19e46c7..599f2e5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,10 +1,10 @@ + android:layout_height="match_parent"> - diff --git a/app/src/main/res/layout/fragment_data.xml b/app/src/main/res/layout/fragment_data.xml index 0a17de9..2e9b4ab 100644 --- a/app/src/main/res/layout/fragment_data.xml +++ b/app/src/main/res/layout/fragment_data.xml @@ -17,7 +17,7 @@ app:layout_constraintTop_toTopOf="parent">