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 e398324..ede5dd9 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/scoutingData/fields.java @@ -2,9 +2,11 @@ package com.astatin3.scoutingapp2025.scoutingData; import com.astatin3.scoutingapp2025.types.input.dropdownType; import com.astatin3.scoutingapp2025.types.input.inputType; +import com.astatin3.scoutingapp2025.types.input.tallyType; import com.astatin3.scoutingapp2025.types.input.textType; import com.astatin3.scoutingapp2025.types.input.sliderType; import com.astatin3.scoutingapp2025.types.input.textType; +import com.astatin3.scoutingapp2025.ui.scouting.TallyCounterView; import com.astatin3.scoutingapp2025.utility.AlertManager; import com.astatin3.scoutingapp2025.utility.fileEditor; import com.astatin3.scoutingapp2025.utility.BuiltByteParser; @@ -31,7 +33,12 @@ public class fields { new sliderType("Test", 128, 64, 256), new dropdownType("test-dropdown", new String[]{"Test1", "test2", "Three"}, 1), new textType("notes", ""), - } + },{ + new tallyType("Test Tally", 0), + new sliderType("Test2", 30, 25, 50), + new dropdownType("test-dropdown", new String[]{"Test1", "test2", "Three"}, 1), + new textType("notes", ""), + } }; public static final inputType[][] default_pit_fields = new inputType[][] { @@ -111,11 +118,13 @@ public class fields { case inputType.notesType: t = new textType(); break; + case inputType.tallyType: + t = new tallyType(); + break; } t.decode((byte[]) obj.get()); - output[i] = t - ; + output[i] = t; } return output; diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/inputType.java b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/inputType.java index 6724cd2..f7fe6a8 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/inputType.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/inputType.java @@ -17,11 +17,13 @@ public abstract class inputType { public static final int slider_type_id = 255; public static final int dropdownType = 254; public static final int notesType = 253; + public static final int tallyType = 252; public enum inputTypes { // USERNAME, SLIDER, DROPDOWN, - NOTES_INPUT + NOTES_INPUT, + TALLY } public String name; public Object default_value; diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/types/input/tallyType.java b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/tallyType.java new file mode 100644 index 0000000..949e0e9 --- /dev/null +++ b/app/src/main/java/com/astatin3/scoutingapp2025/types/input/tallyType.java @@ -0,0 +1,313 @@ +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; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.astatin3.scoutingapp2025.types.data.dataType; +import com.astatin3.scoutingapp2025.types.data.intType; +import com.astatin3.scoutingapp2025.ui.scouting.TallyCounterView; +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.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.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.google.android.material.slider.Slider; +import com.skydoves.powerspinner.IconSpinnerAdapter; +import com.skydoves.powerspinner.IconSpinnerItem; +import com.skydoves.powerspinner.OnSpinnerItemSelectedListener; +import com.skydoves.powerspinner.PowerSpinnerView; +import com.skydoves.powerspinner.SpinnerGravity; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class tallyType extends inputType { + public int get_byte_id() {return tallyType;} + public inputTypes getInputType(){return inputTypes.TALLY;} + public dataType.valueTypes getValueType(){return dataType.valueTypes.NUM;} + public Object get_fallback_value(){return 0;} + public tallyType(){}; + public String get_type_name(){return "Dropdown";} + public tallyType(String name, int default_value){ + super(name); + this.default_value = default_value+1; + } + + + + + + public byte[] encode() throws ByteBuilder.buildingException { + ByteBuilder bb = new ByteBuilder(); + bb.addString(name); + bb.addInt((int)default_value); + return bb.build(); + } + public void decode(byte[] bytes) throws BuiltByteParser.byteParsingExeption { + BuiltByteParser bbp = new BuiltByteParser(bytes); + ArrayList objects = bbp.parse(); + + name = (String) objects.get(0).get(); + default_value = objects.get(1).get(); + } + + + + + + public TallyCounterView tally = null; + + public View createView(Context context, Function onUpdate){ + tally = new TallyCounterView(context); + tally.setOnCountChangedListener(n -> { + onUpdate.apply(getViewValue()); + }); + + setViewValue(default_value); + + return tally; + + }; + public void setViewValue(Object value) { + if(tally == null) return; + System.out.println(value); + if(((int)value) == 0){ + nullify(); + return; + } + + isBlank = false; + tally.setVisibility(View.VISIBLE); + tally.setValue((int)value-1); + } + public void nullify(){ + isBlank = true; + tally.setVisibility(View.GONE); + } + public dataType getViewValue(){ + if(tally == null) return null; + if(tally.getVisibility() == View.GONE) return new intType(name, 0); + return new intType(name, tally.getValue()+1); + } + + + + + + + public void add_individual_view(LinearLayout parent, dataType data){ + if((int)data.get() == 0) return; + + 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(String.valueOf((int) data.get()-1)); + tv.setTextSize(24); + parent.addView(tv); + } + + + + + + + + + private static float calculateMean(int[] data) { + float sum = 0; + for (int value : data) { + sum += (float) value; + } + return sum / data.length; + } + + private static 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 static 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; + } + + private static int findMin(dataType[] data){ + int min = (int)data[0].get()-1; + for(int i = 1; i < data.length; i++) + if((int)data[i].get()-1 < min) + min = (int)data[i].get()-1; + return min; + } + + private static int findMax(dataType[] data){ + int max = (int)data[0].get()-1; + for(int i = 1; i < data.length; i++) + if((int)data[i].get()-1 > max) + max = (int)data[i].get()-1; + return max; + } + + 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 min = findMin(data); + int max = findMax(data); + + int[] values = new int[max-min+1]; + + for (int i = 0; i < data.length; i++) + if((int) data[i].get() != intType.nulval) + values[(int) data[i].get()-min-1]++; + + + ArrayList mean_temp = new ArrayList<>(); + for (int i = 0; i < data.length; i++) + if((int)data[i].get() != 0) + mean_temp.add((int) data[i].get()-1); + + int[] mean_vals = mean_temp.stream().mapToInt(Integer::intValue).toArray(); + + 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(mean_vals); + float stdDev = calculateStandardDeviation(mean_vals, mean); + + // Generate normal distribution curve + List normalDistEntries = generateNormalDistribution(mean-min, stdDev, max-min+1, (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.WHITE); + chart.getAxisLeft().setTextColor(Color.WHITE); + chart.getAxisRight().setTextColor(Color.WHITE); + + Legend legend = chart.getLegend(); + legend.setTextColor(Color.WHITE); + + parent.addView(chart); + } + + + + + public void add_history_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 min = findMin(data); + int max = findMax(data); + + List entries = new ArrayList<>(); + for (int i = 0; i < data.length; i++){ + if(data[i] == null) continue; + + entries.add(new Entry(i, (float)(int) data[i].get()-1)); + } + + + LineDataSet dataSet = new LineDataSet(entries, name); + dataSet.setColor(Color.BLUE); + dataSet.setValueTextColor(Color.BLACK); + dataSet.setDrawCircles(false); + dataSet.setDrawValues(false); + + LineData lineData = new LineData(dataSet); + + 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.WHITE); + chart.getAxisLeft().setTextColor(Color.WHITE); + chart.getAxisRight().setTextColor(Color.WHITE); + + Legend legend = chart.getLegend(); + legend.setTextColor(Color.WHITE); + + + chart.getAxisLeft().setAxisMinimum(min); + chart.getAxisLeft().setAxisMaximum(max); + + chart.getAxisRight().setAxisMinimum(min); + chart.getAxisRight().setAxisMaximum(max); + + + parent.addView(chart); + } +} + diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/MatchScoutingFragment.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/MatchScoutingFragment.java index 6c6c443..c30033a 100644 --- a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/MatchScoutingFragment.java +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/MatchScoutingFragment.java @@ -178,8 +178,8 @@ public class MatchScoutingFragment extends Fragment { // boolean blank = !latest_values[fi].getViewValue().isNull(); // System.out.println(blank); - - asm.update(); + if(asm.isRunning) + update_asm(); if(!DataManager.match_latest_values[fi].isBlank){ tv.setBackgroundColor(0xffff0000); diff --git a/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/TallyCounterView.java b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/TallyCounterView.java new file mode 100644 index 0000000..b6aae97 --- /dev/null +++ b/app/src/main/java/com/astatin3/scoutingapp2025/ui/scouting/TallyCounterView.java @@ -0,0 +1,79 @@ +package com.astatin3.scoutingapp2025.ui.scouting; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.astatin3.scoutingapp2025.R; + +public class TallyCounterView extends LinearLayout { + private int count = 0; + private TextView countDisplay; + private Button minusButton; + private Button plusButton; + private OnCountChangedListener onCountChangedListener; + + public interface OnCountChangedListener { + void onCountChanged(int newCount); + } + + public TallyCounterView(Context context) { + super(context); + init(context); + } + + public TallyCounterView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public TallyCounterView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.view_tally_counter, this, true); + + countDisplay = findViewById(R.id.count_display); + minusButton = findViewById(R.id.minus_button); + plusButton = findViewById(R.id.plus_button); + + updateDisplay(); + + minusButton.setOnClickListener(v -> { + if(count > 0) { + count--; + updateDisplay(); + } + }); + + plusButton.setOnClickListener(v -> { + count++; + updateDisplay(); + }); + } + + private void updateDisplay() { + countDisplay.setText(String.valueOf(count)); + if (onCountChangedListener != null) { + onCountChangedListener.onCountChanged(count); + } + } + + public void setValue(int value) { + count = value; + updateDisplay(); + } + + public int getValue() { + return count; + } + + public void setOnCountChangedListener(OnCountChangedListener listener) { + this.onCountChangedListener = listener; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_tally_counter.xml b/app/src/main/res/layout/view_tally_counter.xml new file mode 100644 index 0000000..08aff4c --- /dev/null +++ b/app/src/main/res/layout/view_tally_counter.xml @@ -0,0 +1,27 @@ + + + +