10 Commits

Author SHA1 Message Date
Michael Mikovsky 79e8e7cbfc Work on adding better field image, some stuff still needs to be fixed 2025-10-08 11:15:17 -06:00
Michael Mikovsky 311dfcbd5d Add TextViewBuilder.java 2025-09-25 15:38:11 -06:00
Michael Mikovsky fa47eb1aff Add some comments 2025-09-21 12:53:59 -06:00
Michael Mikovsky 93b58310bf Update gradle 2025-09-05 10:55:45 -06:00
Michael Mikovsky 859d3bc773 Make delete file menu. Add #8 2025-08-03 22:28:55 -06:00
Michael Mikovsky 9136df04df Add app version info button 2025-08-02 20:22:47 -06:00
Michael Mikovsky f281ff2f0a Fix #16 2025-08-01 18:16:26 -06:00
Michael Mikovsky 154e76fbf7 Add view team on TBA and Statbotics buttons 2025-07-28 14:00:48 -06:00
Michael Mikovsky ffaec948b4 Proper downloading of images 2025-07-28 13:44:06 -06:00
Michael Mikovsky 2d3db09aae Delete file menu, proper downloading of images 2025-07-28 13:43:42 -06:00
46 changed files with 1553 additions and 1403 deletions
+2 -14
View File
@@ -7,9 +7,7 @@
[**Read the wiki**](https://github.com/Team4388/ScoutingApp2025/wiki)
[**Test Data**](https://github.com/Team4388/ScoutingApp2025/blob/main/2024week0-1728149849985.scoutbundle)
#### Here is an overview of the main features currently included in the app:
#### Features
- This project is written for Android! No need for some kind of janky laptop charging setup.
- Similar to ScoutingPASS, there are many diffrent types of fields that can be used to collect data.
- The app is designed to handle updates to the fields on the fly, without loosing any data!
@@ -20,17 +18,7 @@
- Deployment on F-Droid
- Data cloud sync using an FTP server
#### Things that are yet to be implemented:
- A page that lets users cross-compare scouting data between teams. (Compare)
- A page that lets scouters more easily make reports to the drive team before a match starts (Report)
#### Things that may or may not be implemented:
- Statbotics intgration
- Scout error estimation using OPR-like calculation
- - Would most likely require Statbotics
https://www.thebluealliance.com/avatars
### Screenshots
|Match scouting interface|Field editor|Teams data viewer|
|-|-|-|
|![Screenshot1](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/1.png?raw=true)|![Screenshot2](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/2.png?raw=true)|![Screenshot3](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/3.png?raw=true)|
z
|![Screenshot1](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/1.png?raw=true)|![Screenshot2](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/2.png?raw=true)|![Screenshot3](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/3.png?raw=true)|
+4 -6
View File
@@ -44,8 +44,8 @@ android {
buildFeatures {
viewBinding = true
}
aaptOptions {
noCompress("tflite");
androidResources {
noCompress += listOf("tflite")
}
}
@@ -57,13 +57,11 @@ dependencies {
implementation(libs.constraintlayout)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.navigation.fragment.v289)
implementation("androidx.navigation:navigation-fragment:2.8.9")
implementation(libs.navigation.ui)
implementation(libs.preference)
// implementation(libs.asynclayoutinflator)
// implementation(libs.support.annotations)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
@@ -30,7 +30,7 @@ public class Fields {
public static final FieldType[][] default_match_fields = new FieldType[][] {
{
new FieldposType(uuid(),"Auto start pos", "Where does the robot start its auto?", new int[]{0,0}),
new FieldposType(uuid(),"Auto start pos", "Where does the robot start its auto?", FieldposType.DEFAULT_FIELD_IMAGE, new int[]{0,0}),
new TallyType(uuid(),"Auto L4 Coral", "How many coral did this robot score in L4 during auto?", 0),
new TallyType(uuid(),"Auto L3 Coral", "How many coral did this robot score in L3 during auto?", 0),
@@ -42,12 +42,12 @@ public class Fields {
new DropdownType(uuid(),"Auto Quality", "How did the robot drive during auto?", new String[]{"Smooth", "Jittery"}, 0),
new TextType(uuid(),"Auto Comments", "Anything interesting about auto", ""),
new TallyType(uuid(),"Teleop L4 Coral", "How many coral did this robot score in L4 during auto?", 0),
new TallyType(uuid(),"Teleop L3 Coral", "How many coral did this robot score in L3 during auto?", 0),
new TallyType(uuid(),"Teleop L2 Coral", "How many coral did this robot score in L2 during auto?", 0),
new TallyType(uuid(),"Teleop L1 Coral", "How many coral did this robot score in L1 during auto?", 0),
new TallyType(uuid(),"Teleop Processor Algae", "How many algae did this robot score in the Barge during auto?", 0),
new TallyType(uuid(),"Teleop Barge Algae", "How many algae did this robot score in the Barge during auto?", 0),
new TallyType(uuid(),"Teleop L4 Coral", "How many coral did this robot score in L4 during teleop?", 0),
new TallyType(uuid(),"Teleop L3 Coral", "How many coral did this robot score in L3 during teleop?", 0),
new TallyType(uuid(),"Teleop L2 Coral", "How many coral did this robot score in L2 during teleop?", 0),
new TallyType(uuid(),"Teleop L1 Coral", "How many coral did this robot score in L1 during teleop?", 0),
new TallyType(uuid(),"Teleop Processor Algae", "How many algae did this robot score in the Barge during teleop?", 0),
new TallyType(uuid(),"Teleop Barge Algae", "How many algae did this robot score in the Barge during teleop?", 0),
new CheckboxType(uuid(),"Upper Algae Removal", "Did the robot remove upper Algae?", 0),
new CheckboxType(uuid(),"Lower Algae Removal", "Did the robot remove lower Algae?", 0),
@@ -60,61 +60,57 @@ public class ScoutingDataWriter {
public ScoutingArray data;
}
public static ParsedScoutingDataResult load(String filename, FieldType[][] values , TransferType[][] transferValues){
public static ParsedScoutingDataResult load(String filename, FieldType[][] values , TransferType[][] transferValues) throws BuiltByteParser.byteParsingExeption{
byte[] bytes = FileEditor.readFile(filename);
BuiltByteParser bbp = new BuiltByteParser(bytes);
try {
ArrayList<BuiltByteParser.parsedObject> objects = bbp.parse();
RawDataType[] rawDataTypes = new RawDataType[objects.size()-2];
// try {
ArrayList<BuiltByteParser.parsedObject> objects = bbp.parse();
RawDataType[] rawDataTypes = new RawDataType[objects.size()-2];
int version = ((int)objects.get(0).get());
int version = ((int)objects.get(0).get());
if(values.length <= version) {
if(values.length <= version) {
// AlertManager.addSimpleError("Error loading " + filename);
AlertManager.error(new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!"));
return null;
}
throw new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!");
}
// System.out.println(version);
String username = (String) objects.get(1).get();
String username = (String) objects.get(1).get();
for(int i = 0; i < values[version].length; i++){
switch (objects.get(i+2).getType()){
case 1: // Int
rawDataTypes[i] = IntType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break;
case 2: // String
rawDataTypes[i] = StringType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break;
case 3: // Int array
rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")");
break;
}
for(int i = 0; i < values[version].length; i++){
switch (objects.get(i+2).getType()){
case 1: // Int
rawDataTypes[i] = IntType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break;
case 2: // String
rawDataTypes[i] = StringType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break;
case 3: // Int array
rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get());
Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")");
break;
}
ScoutingArray msa = new ScoutingArray(version, rawDataTypes, values, transferValues);
msa.update();
ParsedScoutingDataResult psda = new ParsedScoutingDataResult();
psda.filename = filename;
psda.username = username;
psda.version = version;
psda.data = msa;
return psda;
} catch (BuiltByteParser.byteParsingExeption e){
AlertManager.error(e);
return null;
}
ScoutingArray msa = new ScoutingArray(version, rawDataTypes, values, transferValues);
msa.update();
ParsedScoutingDataResult psda = new ParsedScoutingDataResult();
psda.filename = filename;
psda.username = username;
psda.version = version;
psda.data = msa;
return psda;
// }
}
// A function that takes in a list of names seperated by commas, and adds a name if it is not included
@@ -1,4 +1,179 @@
package com.ridgebotics.ridgescout.types;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.FileEditor;
import java.io.File;
import java.time.Instant;
import java.time.temporal.TemporalField;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
public class ColabArray {
private enum Action {
ADD,
REMOVE
}
private List<Diff> changelog = new ArrayList<>();
private void addChange(Diff change) {
this.changelog.add(change);
}
private List<Diff> getChangelog() {
return changelog;
}
public void add(String item) {
Diff diff = new Diff();
diff.action = Action.ADD;
diff.content = item;
diff.time = new Date();
addChange(diff);
}
public void remove(String item) {
Diff diff = new Diff();
diff.action = Action.REMOVE;
diff.content = item;
diff.time = new Date();
addChange(diff);
}
public void remove(int index) {
remove(get().get(index));
}
private static class Diff {
public Action action;
public String content;
public Date time;
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != this.getClass()) {
return false;
}
Diff other = (Diff) obj;
return other.action == this.action &&
other.time.getTime() == this.time.getTime() &&
other.content.equals(this.content);
}
}
public byte[] encode() throws ByteBuilder.buildingException{
ByteBuilder bb = new ByteBuilder();
for(Diff change : this.changelog){
bb.addInt(change.action.ordinal());
bb.addString(change.content);
bb.addLong(change.time.getTime());
}
return bb.build();
}
public static ColabArray decode(byte[] bytes) throws BuiltByteParser.byteParsingExeption {
BuiltByteParser bbp = new BuiltByteParser(bytes);
List<BuiltByteParser.parsedObject> results = bbp.parse();
if(results.size() % 3 != 0){
throw new BuiltByteParser.byteParsingExeption("Wrong amount of elements in ColabArray!");
}
ColabArray arr = new ColabArray();
for(int i = 0; i < results.size(); i += 3) {
Diff diff = new Diff();
diff.action = Action.values()[(int) results.get(i).get()];
diff.content = (String) results.get(i+1).get();
diff.time = new Date((long) results.get(i+2).get());
arr.addChange(diff);
}
return arr;
}
public void append(ColabArray other) {
List<Diff> otherlog = other.getChangelog();
otherlog.removeIf(diff ->
this.changelog.contains(diff)
);
this.changelog.addAll(otherlog);
this.changelog = Arrays.asList(sort(this.changelog));
}
public void append(File other) {
byte[] bytes = FileEditor.readFile(other);
if(bytes == null) return;
try {
append(decode(bytes));
} catch (BuiltByteParser.byteParsingExeption e) {
AlertManager.error("Failed to append ColabArray!", e);
}
}
private static Diff[] sort(List<Diff> changelog) {
Diff[] sorted = changelog.toArray(new Diff[0]);
try {
Arrays.sort(sorted, (o1, o2) -> (int) (o1.time.getTime() - o2.time.getTime()));
} catch (Exception e){
AlertManager.error(e);
}
return sorted;
}
public List<String> get() {
List<String> result = new ArrayList<>();
for(Diff change : changelog) {
switch (change.action) {
case ADD:
result.add(change.content);
break;
case REMOVE:
result.remove(change.content);
break;
}
}
return result;
}
public boolean contains(String item) {
// Diff[] sorted = sort();
for(int i = changelog.size()-1; i >= 0; i--) {
Diff change = changelog.get(i);
if(!change.content.equals(item)) continue;
return change.action == Action.ADD;
}
return false;
}
public int size() {
return get().size();
}
}
@@ -29,6 +29,7 @@ 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.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -105,16 +106,14 @@ public class DropdownType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return;
TextView tv = new TextView(parent.getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setPadding(20,20,20,20);
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(text_options[(int) data.get()]);
tv.setTextSize(18);
parent.addView(tv);
parent.addView(
new TextViewBuilder(parent.getContext(), text_options[(int) data.get()])
.layout_match_wrap()
.padding(20)
.size(18)
.align_center()
.build());
}
@@ -18,6 +18,7 @@ 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.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.data.RawDataType;
import com.ridgebotics.ridgescout.types.data.IntArrType;
import com.ridgebotics.ridgescout.ui.views.FieldPosView;
@@ -31,14 +32,58 @@ import java.util.Map;
import java.util.function.Function;
public class FieldposType extends FieldType {
public static final FieldImage DEFAULT_FIELD_IMAGE = FieldImage.F2025;
public enum FieldImage {
F2025(0, "2025", R.drawable.field_2025, R.drawable.field_2025_flipped),
F2025_analogous(1, "2025 - analogous", R.drawable.field_2025_analogous);
public int index, resId_normal, resId_flipped;
public String name;
public boolean flippable;
FieldImage(int index, String name, int resId) {
this.index = index;
this.name = name;
this.resId_normal = resId;
this.resId_flipped = resId;
this.flippable = false;
}
FieldImage(int index, String name, int resId_normal, int resId_flipped) {
this.index = index;
this.name = name;
this.resId_normal = resId_normal;
this.resId_flipped = resId_flipped;
this.flippable = true;
}
public static FieldImage from_index(int index) {
return FieldImage.values()[index];
}
public int get_index() {
return this.index;
}
@Override
public String toString() {
return name;
}
}
public FieldImage fieldImage;
public int get_byte_id() {return fieldposType;}
public inputTypes getInputType(){return inputTypes.FIELDPOS;}
public RawDataType.valueTypes getValueType(){return RawDataType.valueTypes.NUM;}
public Object get_fallback_value(){return 0;}
public FieldposType(){}
public String get_type_name(){return "Field Pos";}
public FieldposType(String UUID, String name, String description, int[] default_value){
public FieldposType(String UUID, String name, String description, FieldImage fieldImage, int[] default_value){
super(UUID, name, description);
this.fieldImage = fieldImage;
this.default_value = default_value;
}
@@ -47,11 +92,14 @@ public class FieldposType extends FieldType {
public void encodeData(ByteBuilder bb) throws ByteBuilder.buildingException {
bb.addInt(this.fieldImage.get_index());
bb.addIntArray((int[]) default_value);
}
public void decodeData(ArrayList<BuiltByteParser.parsedObject> objects) {
default_value = objects.get(0).get();
fieldImage = FieldImage.from_index((int) objects.get(0).get());
default_value = objects.get(1).get();
}
@@ -65,6 +113,7 @@ public class FieldposType extends FieldType {
onUpdate.apply(new IntArrType(name, pos));
});
setViewValue(default_value);
field.setFieldImage(fieldImage);
return field;
}
@@ -102,6 +151,7 @@ public class FieldposType extends FieldType {
FieldPosView fp = new FieldPosView(parent.getContext());
fp.setEnabled(false);
fp.setPos((int[]) data.get());
fp.setFieldImage(this.fieldImage);
parent.addView(fp);
}
@@ -113,54 +163,13 @@ public class FieldposType extends FieldType {
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 += (float) Math.pow((float) value - mean, 2);
}
return (float) Math.sqrt(sum / (data.length - 1));
}
private static List<Entry> generateNormalDistribution(float mean, float stdDev, int count, int scale) {
List<Entry> entries = new ArrayList<>();
for (int i = 0; i < count; i++) {
float y = (float) ((1 / (stdDev * Math.sqrt(2 * Math.PI)))
* Math.exp(-0.5 * Math.pow(((float) i - mean) / stdDev, 2)));
entries.add(new Entry((float) i, y*scale)); // Scale y for visibility
}
return entries;
}
private static int findMin(RawDataType[] data){
int min = (int)data[0].get();
for(int i = 1; i < data.length; i++)
if((int)data[i].get() < min)
min = (int)data[i].get();
return min;
}
private static int findMax(RawDataType[] data){
int max = (int)data[0].get();
for(int i = 1; i < data.length; i++)
if((int)data[i].get() > max)
max = (int)data[i].get();
return max;
}
public void add_compiled_view(LinearLayout parent, RawDataType[] data){
MultiFieldPosView mfp = new MultiFieldPosView(parent.getContext());
for(int i = 0; i < data.length; i++){
if(data[i].isNull()) continue;
mfp.addPos((int[]) data[i].get());
}
mfp.setFieldImage(fieldImage);
parent.addView(mfp);
}
@@ -29,6 +29,7 @@ import com.ridgebotics.ridgescout.types.data.RawDataType;
import com.ridgebotics.ridgescout.types.data.IntType;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -118,16 +119,11 @@ public class NumberType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) 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()));
tv.setTextSize(24);
parent.addView(tv);
parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get()))
.layout_match_wrap()
.align_center()
.size(24)
.build());
}
@@ -29,6 +29,7 @@ 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.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Collections;
@@ -103,16 +104,11 @@ public class TallyType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) 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()));
tv.setTextSize(24);
parent.addView(tv);
parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get()))
.layout_match_wrap()
.align_center()
.size(24)
.build());
}
@@ -344,16 +340,12 @@ public class TallyType extends FieldType {
row = new TableRow(parent.getContext());
CandlestickView view = views.get(i);
TextView teamNum = new TextView(parent.getContext());
TableRow.LayoutParams params = new TableRow.LayoutParams();
params.gravity = Gravity.CENTER;
teamNum.setLayoutParams(params);
teamNum.setPadding(10,10,10,10);
teamNum.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
teamNum.setText(String.valueOf(view.teamNum));
row.addView(new TextViewBuilder(parent.getContext(), String.valueOf(view.teamNum))
.align_center()
.padding(10)
.h6()
.build());
row.addView(teamNum);
row.addView(view);
parent.addView(row);
@@ -28,6 +28,7 @@ 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.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -112,15 +113,11 @@ public class TextType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) 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) data.get());
tv.setTextSize(18);
parent.addView(tv);
parent.addView(new TextViewBuilder(parent.getContext(), (String) data.get())
.layout_match_wrap()
.align_center()
.size(18)
.build());
}
@@ -144,25 +141,20 @@ public class TextType extends FieldType {
positive_mean = 0;
count = 0;
positive_text = new TextView(parent.getContext());
positive_text.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
positive_text.setGravity(Gravity.CENTER_HORIZONTAL);
positive_text.setTextSize(20);
positive_text = new TextViewBuilder(parent.getContext())
.align_center()
.size(20)
.build();
parent.addView(positive_text);
for (int i = 0; i < data.length; i++){
if (!data[i].isNull()) {
SentimentAnalysis.analyse((String) data[i].get(), new SentimentAnalysis.resultCallback() {
@Override
public void onFinish(float sentiment) {
positive_mean += sentiment;
count++;
SentimentAnalysis.analyse((String) data[i].get(), sentiment -> {
positive_mean += sentiment;
count++;
positive_text.setText("Sentiment: " + (positive_mean / count));
}
positive_text.setText("Sentiment: " + (positive_mean / count));
});
}
}
@@ -19,12 +19,9 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.frcTeam;
import com.ridgebotics.ridgescout.ui.views.FieldBorderedRow;
import com.ridgebotics.ridgescout.ui.views.RecyclerList;
import com.ridgebotics.ridgescout.ui.views.TeamListOption;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.SettingsManager;
@@ -47,7 +44,7 @@ public class DataFragment extends Fragment {
@Nullable Bundle savedInstanceState) {
binding = FragmentDataBinding.inflate(inflater, container, false);
// binding.table.setStretchAllColumns(true);
binding.table.setStretchAllColumns(true);
View root = binding.getRoot();
if(evcode == null || evcode.equals("unset") || event == null){
@@ -89,21 +86,35 @@ public class DataFragment extends Fragment {
public void load_teams(){
RecyclerList<frcTeam> list = new RecyclerList<>(getContext());
binding.table.addView(list);
// list.setView
int[] teamNums = new int[event.teams.size()];
list
.setup(R.layout.view_team_option, TeamListOption::new)
.withLinearLayout()
.withDivider()
.withItemClickListener((team, position) -> {
TeamsFragment.setTeam(team);
for(int i = 0 ; i < event.teams.size(); i++){
teamNums[i] = event.teams.get(i).teamNumber;
}
Arrays.sort(teamNums);
for(int i = 0; i < event.teams.size(); i++){
frcTeam team = null;
for(int a = 0 ; a < event.teams.size(); a++){
if(event.teams.get(a).teamNumber == teamNums[i]){
team = event.teams.get(a);
break;
}
}
assert team != null;
TeamListOption teamRow = new TeamListOption(getContext());
binding.table.addView(teamRow);
teamRow.fromTeam(team);
frcTeam finalTeam = team;
teamRow.setOnClickListener(v -> {
TeamsFragment.setTeam(finalTeam);
((DataParentFragment) getParentFragment()).moveToFragment(new TeamsFragment());
// findNavController(this).navigate(R.id.action_navigation_data_parent_to_navigation_data_teams);
});
list.setItems(event.getTeamsSorted());
}
}
public void load_fields(){
DataManager.reload_match_fields();
@@ -58,7 +58,7 @@ public class FieldDataFragment extends Fragment {
for (int teamIndex = 0; teamIndex < event.teams.size(); teamIndex++) {
int teamNum = event.teams.get(teamIndex).teamNumber;
List<String> filenames = new ArrayList<>(List.of(FileEditor.getMatchesByTeamNum(evcode, event.teams.get(teamIndex).teamNumber)));
filenames.removeAll(rescout_list);
filenames.removeAll(rescout_list.get());
ArrayList<RawDataType> teamData = new ArrayList<>();
@@ -10,6 +10,8 @@ import static com.ridgebotics.ridgescout.utility.DataManager.pit_latest_values;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_values;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -23,6 +25,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentDataTeamsBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
@@ -30,6 +33,7 @@ import com.ridgebotics.ridgescout.types.data.RawDataType;
import com.ridgebotics.ridgescout.types.frcTeam;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.List;
@@ -66,38 +70,37 @@ public class TeamsFragment extends Fragment {
loadTeam(index);
});
binding.tbaButton.setOnClickListener(v -> openWebPage(
"https://www.thebluealliance.com/team/"+team.teamNumber+"/"+SettingsManager.getYearNum()
));
binding.statboticsButton.setOnClickListener(v -> openWebPage(
"https://www.statbotics.io/team/"+team.teamNumber+"/"+SettingsManager.getYearNum()
));
loadTeam(SettingsManager.getTeamsDataMode());
return binding.getRoot();
}
public void openWebPage(String url) {
Uri webpage = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
// if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(intent);
// }
}
public void loadTeam(int mode) {
// LinearLayout ll = new LinearLayout(getContext());
// ll.setLayoutParams(new LinearLayout.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.WRAP_CONTENT
// ));
// ll.setOrientation(LinearLayout.VERTICAL);
// binding.teamsArea.addView(ll);
binding.dataTeamCard.fromTeam(team);
// 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.getDescription());
// tv.setTextSize(16);
// ll.addView(tv);
try {add_pit_data(team);}catch(Exception e){AlertManager.error(e);}
try {add_match_data(team, mode);}catch(Exception e){AlertManager.error(e);}
}
public void add_pit_data(frcTeam team){
public void add_pit_data(frcTeam team) throws BuiltByteParser.byteParsingExeption {
binding.pitArea.removeAllViews();
final String filename = evcode+"-"+team.teamNumber+".pitscoutdata";
@@ -117,49 +120,34 @@ public class TeamsFragment extends Fragment {
// ll.addView(new MaterialDivider(getContext()));
if(!FileEditor.fileExist(filename)){
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("No pit data has been collected!");
tv.setTextSize(23);
binding.pitArea.addView(tv);
binding.pitArea.addView(new TextViewBuilder(getContext(), "No pit data has been collected!")
.layout_match_wrap().align_center().size(23).build());
return;
}
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filename, pit_values, pit_transferValues);
TextView 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);
binding.pitArea.addView(tv);
binding.pitArea.addView(new TextViewBuilder(getContext(), "Pit scouting by " + psda.username)
.layout_match_wrap()
.padding(0, 20, 0, 5)
.align_center()
.size(30)
.build()
);
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(pit_latest_values[a].name);
tv.setTextSize(25);
TextViewBuilder tvb = new TextViewBuilder(getContext(), pit_latest_values[a].name)
.align_center()
.layout_match_wrap()
.size(25);
if(psda.data.array[a].isNull()){
tv.setBackgroundColor(toggletitle_unselected);
tv.setTextColor(toggletitle_black_background);
tvb.tv.setBackgroundColor(toggletitle_unselected);
tvb.tv.setTextColor(toggletitle_black_background);
}
binding.pitArea.addView(tv);
binding.pitArea.addView(tvb.build());
pit_latest_values[a].add_individual_view(binding.pitArea, psda.data.array[a]);
@@ -175,15 +163,11 @@ public class TeamsFragment extends Fragment {
String[] files = FileEditor.getMatchesByTeamNum(evcode, team.teamNumber);
if(files.length == 0){
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("No match data has been collected!");
tv.setTextSize(23);
binding.matchArea.addView(tv);
binding.matchArea.addView(new TextViewBuilder(getContext(), "No match data has been collected!")
.layout_match_wrap()
.align_center()
.size(23)
.build());
return;
}
@@ -236,33 +220,28 @@ public class TeamsFragment extends Fragment {
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[matchIndex], match_values, match_transferValues);
TextView tv = new TextView(getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setPadding(0, 40, 0, 5);
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username);
tv.setTextSize(30);
binding.matchArea.addView(tv);
binding.matchArea.addView(
new TextViewBuilder(getContext(), "M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username)
.align_center()
.size(30)
.padding(0,0,40,5)
.build()
);
for (int i = 0; i < psda.data.array.length; i++) {
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_latest_values[i].name);
tv.setTextSize(25);
TextViewBuilder tv = new TextViewBuilder(getContext(), match_latest_values[i].name)
.align_center()
.size(25);
if (psda.data.array[i].isNull()) {
tv.setBackgroundColor(toggletitle_unselected);
tv.setTextColor(toggletitle_black_background);
tv.tv.setBackgroundColor(toggletitle_unselected);
tv.tv.setTextColor(toggletitle_black_background);
}
binding.matchArea.addView(tv);
binding.matchArea.addView(tv.build());
if(psda.data.array[i] != null)
@@ -294,16 +273,14 @@ public class TeamsFragment extends Fragment {
}
for(int i = 0; i < match_latest_values.length; i++){
TextView 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_latest_values[i].name);
tv.setTextSize(30);
binding.matchArea.addView(tv);
binding.matchArea.addView(
new TextViewBuilder(getContext(), match_latest_values[i].name)
.align_center()
.padding(0, 0, 20, 5)
.size(30)
.build()
);
if(data[i] != null)
match_latest_values[i].add_compiled_view(binding.matchArea, data[i]);
@@ -329,19 +306,18 @@ public class TeamsFragment extends Fragment {
}
for(int i = 0; i < match_latest_values.length; i++){
TextView 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_latest_values[i].name);
tv.setTextSize(30);
binding.matchArea.addView(tv);
binding.matchArea.addView(
new TextViewBuilder(getContext(), match_latest_values[i].name)
.align_center()
.size(30)
.padding(0,0,20,5)
.build()
);
if(data[i] != null)
match_latest_values[i].add_history_view(binding.matchArea, data[i]);
}
}
}
@@ -30,6 +30,7 @@ import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.types.frcMatch;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -59,11 +60,10 @@ public class EventFragment extends Fragment {
add_match_scouting(event);
}
private void addTableText(TableRow tr, String textStr){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center
text.setText(textStr);
tr.addView(text);
tr.addView(new TextViewBuilder(getContext(), textStr)
.align_center()
.size(18)
.build());
}
public void add_pit_scouting(frcEvent event){
@@ -94,33 +94,32 @@ public class EventFragment extends Fragment {
tr = new TableRow(getContext());
}
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
TextViewBuilder text = new TextViewBuilder(getContext(), String.valueOf(num))
.size(18)
.align_center();
text.setText(String.valueOf(num));
final String filename = event.eventCode + "-" + num + ".pitscoutdata";
if(FileEditor.fileExist(filename)){
final boolean[] rescout = {DataManager.rescout_list.contains(filename)};
text.setBackgroundColor(rescout[0] ? color_rescout : color_found);
text.tv.setBackgroundColor(rescout[0] ? color_rescout : color_found);
text.setOnLongClickListener(view -> {
text.tv.setOnLongClickListener(view -> {
rescout[0] = !rescout[0];
if(rescout[0]) {
text.setBackgroundColor(color_rescout);
text.tv.setBackgroundColor(color_rescout);
DataManager.rescout_list.add(filename);
}else{
text.setBackgroundColor(color_found);
text.tv.setBackgroundColor(color_found);
DataManager.rescout_list.remove(filename);
}
DataManager.save_rescout_list();
return true;
});
}else{
text.setBackgroundColor(color_not_found);
text.tv.setBackgroundColor(color_not_found);
}
tr.addView(text);
tr.addView(text.build());
}
if(tr != null)
binding.teamsTable.addView(tr);
@@ -153,10 +152,6 @@ public class EventFragment extends Fragment {
addTableText(tr, String.valueOf(match.matchIndex));
//
for(int i=0;i<6;i++){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
int team_num;
String alliance_position;
@@ -168,20 +163,23 @@ public class EventFragment extends Fragment {
alliance_position = "blue-"+(i-2);
}
text.setText(String.valueOf(team_num));
TextViewBuilder text = new TextViewBuilder(getContext(), String.valueOf(team_num))
.size(18)
.align_center();
final String filename = event.eventCode + "-" + match.matchIndex + "-" + alliance_position + "-" + team_num + ".matchscoutdata";
if(FileEditor.fileExist(filename)){
final boolean[] rescout = {DataManager.rescout_list.contains(filename)};
text.setBackgroundColor(rescout[0] ? color_rescout : color_found);
text.tv.setBackgroundColor(rescout[0] ? color_rescout : color_found);
text.setOnLongClickListener(view -> {
text.tv.setOnLongClickListener(view -> {
rescout[0] = !rescout[0];
if(rescout[0]) {
text.setBackgroundColor(color_rescout);
text.tv.setBackgroundColor(color_rescout);
DataManager.rescout_list.add(filename);
}else{
text.setBackgroundColor(color_found);
text.tv.setBackgroundColor(color_found);
DataManager.rescout_list.remove(filename);
}
DataManager.save_rescout_list();
@@ -189,9 +187,9 @@ public class EventFragment extends Fragment {
});
}else{
text.setBackgroundColor(color_not_found);
text.tv.setBackgroundColor(color_not_found);
}
tr.addView(text);
tr.addView(text.build());
}
binding.matchTable.addView(tr);
@@ -21,6 +21,7 @@ import androidx.fragment.app.Fragment;
import com.google.android.material.divider.MaterialDivider;
import com.ridgebotics.ridgescout.ui.views.ToggleTitleView;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingMatchBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
@@ -32,6 +33,7 @@ import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.AutoSaveManager;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
// Fragment for match scouting data editing.
public class MatchScoutingFragment extends Fragment {
@@ -58,10 +60,12 @@ public class MatchScoutingFragment extends Fragment {
binding.matchTeamCard.setVisibility(View.VISIBLE);
if(DataManager.match_values == null || DataManager.match_values.length == 0){
TextView tv = new TextView(getContext());
tv.setText("Failed to load fields.\nTry to either download or create match scouting fields.");
tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
binding.MatchScoutArea.addView(tv);
binding.MatchScoutArea.addView(
new TextViewBuilder(getContext(), "Failed to load fields.\nTry to either download or create match scouting fields.")
.align_center()
.build());
return binding.getRoot();
}
@@ -347,7 +351,7 @@ public class MatchScoutingFragment extends Fragment {
public void get_fields(){
public void get_fields() throws BuiltByteParser.byteParsingExeption{
ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.match_values, DataManager.match_transferValues);
RawDataType[] types = psdr.data.array;
@@ -23,6 +23,7 @@ import com.google.android.material.divider.MaterialDivider;
import com.ridgebotics.ridgescout.ui.views.PitScoutingIndicator;
import com.ridgebotics.ridgescout.ui.views.ToggleTitleView;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingPitBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
@@ -224,7 +225,7 @@ public class PitScoutingFragment extends Fragment {
}
}
public void get_fields(){
public void get_fields() throws BuiltByteParser.byteParsingExeption{
ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, pit_values, pit_transferValues);
RawDataType[] types = psdr.data.array;
@@ -51,89 +51,89 @@ public class PitSelectorFragment extends Fragment {
return binding.getRoot();
}
// load_teams();
load_teams();
return binding.getRoot();
}
// public void load_teams(){
//// binding.pitFileIndicator.setVisibility(View.GONE);
//// binding.pitTeamName.setVisibility(View.GONE);
//// binding.pitTeamDescription.setVisibility(View.GONE);
////
//// clear_fields();
public void load_teams(){
// binding.pitFileIndicator.setVisibility(View.GONE);
// binding.pitTeamName.setVisibility(View.GONE);
// binding.pitTeamDescription.setVisibility(View.GONE);
//
//
// int[] teamNums = new int[event.teams.size()];
//
// for(int i = 0 ; i < event.teams.size(); i++){
// teamNums[i] = event.teams.get(i).teamNumber;
// }
//
// Arrays.sort(teamNums);
//
// TableLayout table = new TableLayout(getContext());
// table.setStretchAllColumns(true);
// binding.teams.addView(table);
//
//
// for(int i = 0; i < event.teams.size(); i++){
// frcTeam team = null;
// for(int a = 0 ; a < event.teams.size(); a++){
// if(event.teams.get(a).teamNumber == teamNums[i]){
// team = event.teams.get(a);
// break;
// }
// }
// assert team != null;
//
//// TableRow tr = new TableRow(getContext());
//// TableLayout.LayoutParams rowParams = new TableLayout.LayoutParams(
//// FrameLayout.LayoutParams.WRAP_CONTENT,
//// FrameLayout.LayoutParams.WRAP_CONTENT
//// );
//// rowParams.setMargins(20,20,20,20);
//// tr.setLayoutParams(rowParams);
//// tr.setPadding(20,20,20,20);
//// table.addView(tr);
//
// TeamListOption teamRow = new TeamListOption(getContext());
// table.addView(teamRow);
// teamRow.fromTeam(team);
//
//
// String filename = evcode + "-" + team.teamNumber + ".pitscoutdata";
//
// if (FileEditor.fileExist(filename)) {
// final boolean[] rescout = {DataManager.rescout_list.contains(filename)};
//
// teamRow.setColor(DataManager.rescout_list.contains(filename) ? color_rescout : color_found);
//
// teamRow.setOnLongClickListener(v -> {
// rescout[0] = !rescout[0];
// if(rescout[0]){
// DataManager.rescout_list.add(filename);
// teamRow.setColor(color_rescout);
// DataManager.save_rescout_list();
// }else{
// DataManager.rescout_list.remove(filename);
// teamRow.setColor(color_found);
// DataManager.save_rescout_list();
// }
//
//
// return true;
// });
// } else {
// teamRow.setColor(color_not_found);
// teamRow.setOnLongClickListener(v -> true);
// }
//
//
// frcTeam finalTeam = team;
// teamRow.setOnClickListener(v -> onSelect.onSelect(this, finalTeam));
// }
// }
// clear_fields();
int[] teamNums = new int[event.teams.size()];
for(int i = 0 ; i < event.teams.size(); i++){
teamNums[i] = event.teams.get(i).teamNumber;
}
Arrays.sort(teamNums);
TableLayout table = new TableLayout(getContext());
table.setStretchAllColumns(true);
binding.teams.addView(table);
for(int i = 0; i < event.teams.size(); i++){
frcTeam team = null;
for(int a = 0 ; a < event.teams.size(); a++){
if(event.teams.get(a).teamNumber == teamNums[i]){
team = event.teams.get(a);
break;
}
}
assert team != null;
// TableRow tr = new TableRow(getContext());
// TableLayout.LayoutParams rowParams = new TableLayout.LayoutParams(
// FrameLayout.LayoutParams.WRAP_CONTENT,
// FrameLayout.LayoutParams.WRAP_CONTENT
// );
// rowParams.setMargins(20,20,20,20);
// tr.setLayoutParams(rowParams);
// tr.setPadding(20,20,20,20);
// table.addView(tr);
TeamListOption teamRow = new TeamListOption(getContext());
table.addView(teamRow);
teamRow.fromTeam(team);
String filename = evcode + "-" + team.teamNumber + ".pitscoutdata";
if (FileEditor.fileExist(filename)) {
final boolean[] rescout = {DataManager.rescout_list.contains(filename)};
teamRow.setColor(DataManager.rescout_list.contains(filename) ? color_rescout : color_found);
teamRow.setOnLongClickListener(v -> {
rescout[0] = !rescout[0];
if(rescout[0]){
DataManager.rescout_list.add(filename);
teamRow.setColor(color_rescout);
DataManager.save_rescout_list();
}else{
DataManager.rescout_list.remove(filename);
teamRow.setColor(color_found);
DataManager.save_rescout_list();
}
return true;
});
} else {
teamRow.setColor(color_not_found);
teamRow.setOnLongClickListener(v -> true);
}
frcTeam finalTeam = team;
teamRow.setOnClickListener(v -> onSelect.onSelect(this, finalTeam));
}
}
}
@@ -28,6 +28,7 @@ import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingBinding;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Set;
@@ -169,16 +170,15 @@ public class ScoutingFragment extends Fragment {
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
TextView nextMatchText = new TextView(getContext());
nextMatchText.setText("Our next match: Match " + nextMatch);
nextMatchText.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
binding.infoBox.addView(nextMatchText);
binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch)
.body1()
.build());
int informedBy = event.getMostInformedBy(teamNum, curMatchNum);
TextView mostInformedText = new TextView(getContext());
mostInformedText.setText("Most informed by: Match " + informedBy);
mostInformedText.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
binding.infoBox.addView(mostInformedText);
binding.infoBox.addView(new TextViewBuilder(getContext(), "Most informed by: Match " + informedBy)
.body1()
.build());
}
}
@@ -18,9 +18,14 @@ import com.ridgebotics.ridgescout.types.input.NumberType;
import com.ridgebotics.ridgescout.types.input.SliderType;
import com.ridgebotics.ridgescout.types.input.TallyType;
import com.ridgebotics.ridgescout.types.input.TextType;
import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
// Class to help with fields editor fragment, containing the defaults for each field.
@@ -29,7 +34,8 @@ public class FieldEditorHelper {
paramNumber,
paramString,
paramStringArray,
paramNumberArray
paramNumberArray,
paramDropdown
}
public static class parameterType {
@@ -64,6 +70,17 @@ public class FieldEditorHelper {
}
}
public static class paramDropdown extends parameterType{
public List<String> options;
public int val;
public paramDropdown(String name, List<String> options, int val){
this.name = name + " (Dropdown)";
this.options = options;
this.val = val;
this.id = parameterTypeEnum.paramDropdown;
}
}
// public static class paramNumberArray extends parameterType{
// public int[] val;
// public paramNumberArray(String name, int[] val){
@@ -166,9 +183,16 @@ public class FieldEditorHelper {
}
private static parameterType[] getFieldPosParam(FieldposType s){
FieldposType.FieldImage[] f_images = FieldposType.FieldImage.values();
List<String> images = new ArrayList<>();
for (FieldposType.FieldImage fimage: f_images) {
images.add(fimage.toString());
}
return new parameterType[]{
new paramString("Name", s.name),
new paramString("Description", s.description),
new paramDropdown("Field Image", images, 0),
new paramNumber("Default X", ((int[]) s.default_value)[0]),
new paramNumber("Default Y", ((int[]) s.default_value)[1])
};
@@ -218,9 +242,10 @@ public class FieldEditorHelper {
public static void setFieldPosParam(FieldposType s, parameterType[] types){
s.name = ((paramString) types[0]).val;
s.description = ((paramString) types[1]).val;
s.fieldImage = FieldposType.FieldImage.from_index(((paramDropdown) types[2]).val);
s.default_value = new int[]{
((paramNumber) types[2]).val,
((paramNumber) types[3]).val
((paramNumber) types[3]).val,
((paramNumber) types[4]).val
};
}
@@ -306,6 +331,24 @@ public class FieldEditorHelper {
return text;
}
private static View createDropdown(Context c, String name, List<String> options, int value){
CustomSpinnerView spinner = new CustomSpinnerView(c);
spinner.setTitle(name);
spinner.setOptions(options, value);
spinner.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
// EditText text = new EditText(c);
// text.setText(String.join("\n", value));
// text.setLayoutParams(new LinearLayout.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.WRAP_CONTENT
// ));
return spinner;
}
private static View createEdit(Context c, parameterType t){
switch (t.id){
case paramNumber:
@@ -314,6 +357,8 @@ public class FieldEditorHelper {
return createStringEdit(c, ((paramString) t).val);
case paramStringArray:
return createStringArrayEdit(c, ((paramStringArray) t).val);
case paramDropdown:
return createDropdown(c, t.name, ((paramDropdown) t).options, ((paramDropdown) t).val);
}
return null;
}
@@ -321,22 +366,27 @@ public class FieldEditorHelper {
private static boolean readEdit(View v, parameterType t){
try{
String val;
// String val;
switch (t.id) {
case paramNumber:
val = ((EditText) v).getText().toString();
if(val.isEmpty() || val.isBlank()) return false;
((paramNumber) t).val = Integer.parseInt(val);
String val1 = ((EditText) v).getText().toString();
if(val1.isEmpty() || val1.isBlank()) return false;
((paramNumber) t).val = Integer.parseInt(val1);
break;
case paramString:
val = ((EditText) v).getText().toString();
String val2 = ((EditText) v).getText().toString();
//if(val.isEmpty() || val.isBlank()) return false;
((paramString) t).val = val;
((paramString) t).val = val2;
break;
case paramStringArray:
val = ((EditText) v).getText().toString();
if(val.isEmpty() || val.isBlank()) return false;
((paramStringArray) t).val = val.split("\n");
String val3 = ((EditText) v).getText().toString();
if(val3.isEmpty() || val3.isBlank()) return false;
((paramStringArray) t).val = val3.split("\n");
break;
case paramDropdown:
int val4 = ((CustomSpinnerView) v).getIndex();
// if(val.isEmpty() || val.isBlank()) return false;
((paramDropdown) t).val = val4;
break;
}
} catch (Exception e) {
@@ -397,11 +447,11 @@ public class FieldEditorHelper {
this.t = t;
views = new View[types.length];
for(int i = 0; i < types.length; i++){
TextView tv = new TextView(c);
tv.setText(types[i].name);
tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
tv.setTextSize(20);
parentView.addView(tv);
parentView.addView(new TextViewBuilder(c, types[i].name)
.align_center()
.size(20)
.build());
views[i] = createEdit(c, types[i]);
parentView.addView(views[i]);
@@ -30,6 +30,7 @@ import com.ridgebotics.ridgescout.types.input.FieldType;
import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
import com.ridgebotics.ridgescout.ui.views.FieldDisplay;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Collections;
@@ -196,10 +197,9 @@ public class FieldsFragment extends Fragment {
sv.addView(table);
TextView UUID = new TextView(getContext());
UUID.setText("Type: " + field.get_type_name() + "\nUUID: " + field.UUID);
table.addView(UUID);
table.addView(new TextViewBuilder(getContext(), "Type: " + field.get_type_name() + "\nUUID: " + field.UUID)
.build());
FieldEditorHelper f = new FieldEditorHelper(getContext(), field, table);
@@ -1,6 +1,5 @@
package com.ridgebotics.ridgescout.ui.settings;
import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import static androidx.navigation.fragment.FragmentKt.findNavController;
@@ -21,6 +20,9 @@ import static com.ridgebotics.ridgescout.utility.SettingsManager.prefs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
@@ -29,12 +31,9 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -51,11 +50,11 @@ import com.ridgebotics.ridgescout.databinding.FragmentSettingsBinding;
import com.ridgebotics.ridgescout.scoutingData.Fields;
import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
import com.ridgebotics.ridgescout.ui.views.TallyCounterView;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import org.w3c.dom.Text;
import com.ridgebotics.ridgescout.utility.ToDelete;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,9 +85,19 @@ public class SettingsFragment extends Fragment {
SettingsManager manager = new SettingsManager(getContext());
ButtonSettingsItem appInfoButton = new ButtonSettingsItem();
appInfoButton.addButton("App info", v -> showAppInfo());
manager.addItem(appInfoButton);
ButtonSettingsItem corruptButton = new ButtonSettingsItem();
corruptButton.addButton("Remove corrupted files", view -> {});
corruptButton.addButton("find corrupted files", view -> {
ToDelete.findCorruptedFiles(getContext());
});
corruptButton.addButton("delete files", view -> {
ToDelete.deleteFiles(getContext(), Arrays.asList(FileEditor.getFiles()), false);
});
// corruptButton.setEnabled(!getEVCode().equals("unset"));
manager.addItem(corruptButton);
manager.addItem(new HeaderSettingsItem("Advanced"));
@@ -142,10 +151,7 @@ public class SettingsFragment extends Fragment {
manager.addItem(new CheckboxSettingsItem(EnableQuickAllianceChangeKey, "Enable quick alliance swap", null));
manager.addItem(new DropdownSettingsItem(FieldImageKey, "Field Image", new String[]{
"2025",
"2025 (Flipped)"
}));
manager.addItem(new CheckboxSettingsItem(FieldImageKey, "Field Image", null));
manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list));
@@ -244,6 +250,41 @@ public class SettingsFragment extends Fragment {
private TextView createText(String title) {
return new TextViewBuilder(getContext(), title)
.body1().build();
}
private void showAppInfo() {
LinearLayout ll = new LinearLayout(getContext());
ll.setOrientation(VERTICAL);
ll.setPadding(10, 10, 10, 10);
try {
PackageInfo pInfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0);
ll.addView(createText("Package: " + pInfo.packageName));
ll.addView(createText("Version: " + pInfo.versionName));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ll.addView(createText("Signature: " + (pInfo.signingInfo != null ? "True" : "False")));
}
} catch (PackageManager.NameNotFoundException e) {
AlertManager.error("Failed to get version info", e);
}
AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle("App info");
alert.setView(ll);
alert.setNeutralButton("Ok", null);
alert.setCancelable(true);
alert.create().show();
}
@@ -350,9 +391,8 @@ public class SettingsFragment extends Fragment {
@Override
public View createView(Context context) {
TextView titleView = new TextView(context);
titleView.setText(getTitle());
titleView.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
TextView titleView = new TextViewBuilder(context, getTitle())
.sub1().build();
TextInputLayout textInputLayout = new TextInputLayout(context);
editText = new TextInputEditText(context);
@@ -432,11 +472,10 @@ public class SettingsFragment extends Fragment {
});
tally.setEnabled(enabled);
TextView tv = new TextView(getContext());
tv.setText(getTitle());
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
tv.setGravity(Gravity.CENTER);
ll.addView(tv);
ll.addView(new TextViewBuilder(getContext(), getTitle())
.h6()
.build());
ll.addView(tally);
@@ -560,10 +599,9 @@ public class SettingsFragment extends Fragment {
ll.setOrientation(VERTICAL);
ll.setPadding(0, 20,0,0);
TextView tv = new TextView(context);
tv.setText(title);
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline4);
ll.addView(tv);
ll.addView(new TextViewBuilder(context, title)
.h4()
.build());
ll.addView(new MaterialDivider(context));
@@ -1,272 +0,0 @@
package com.ridgebotics.ridgescout.ui.transfer;
import static com.ridgebotics.ridgescout.utility.DataManager.evcode;
import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir;
import android.util.Log;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
// This is now deprecated. use HTTPSync
// Class to synchronise data over FTP.
public class FTPSync extends Thread {
public static final String remoteBasePath = "/RidgeScout/";
public static final String timestampsFilename = "timestamps";
public static long lastSyncTime = 0;
private static Date curSyncTime;
private static final long millisTolerance = 1000;
private boolean after(Date a, Date b){
return a.getTime() - b.getTime() > millisTolerance;
}
public interface onResult {
void onResult(boolean error, int upCount, int downCount);
}
public interface UpdateIndicator {
void onText(String text);
}
private static UpdateIndicator updateIndicator = text -> {};
public static String text = "";
private static void setUpdateIndicator(String m_text){
text = m_text;
updateIndicator.onText(m_text);
}
public static void setOnUpdateIndicator(UpdateIndicator m_updateIndicator){
updateIndicator = m_updateIndicator;
}
private static onResult onResult = (error, upCount, downCount) -> {};
public static void setOnResult(onResult result){
onResult = result;
}
private static boolean isRunning = false;
public static boolean getIsRunning(){return isRunning;}
public static void sync(){
// DataManager.reload_event();
FTPSync ftpSync = new FTPSync();
curSyncTime = new Date();
ftpSync.start();
}
FTPClient ftpClient;
private int upCount = 0;
private int downCount = 0;
private void downloadFile(String remoteFile, File localFile) throws IOException {
try (FileOutputStream fos = new FileOutputStream(localFile)) {
ftpClient.retrieveFile(remoteBasePath + remoteFile, fos);
}
}
private void uploadFile(File localFile) throws IOException {
try (FileInputStream fis = new FileInputStream(localFile)) {
ftpClient.storeFile(remoteBasePath + localFile.getName(), fis);
}
}
private FTPFile findRemoteFile(FTPFile[] remoteFiles, String fileName) {
for (FTPFile file : remoteFiles) {
if (file.getName().equals(fileName)) {
return file;
}
}
return null;
}
private Date getUtcTimestamp(FTPFile file) {
return file.getTimestamp().getTime();
}
private Date getLocalFileUtcTimestamp(File file) {
return new Date(file.lastModified());
}
private void setLocalFileTimestamp(File file, Date date) {
file.setLastModified(date.getTime());
}
public void run() {
isRunning = true;
boolean sendMetaFiles = SettingsManager.getFTPSendMetaFiles();
// Meta files
List<String> meta_string_array = Arrays.asList(
"matches.fields",
"pits.fields",
evcode+".eventdata"
);
try {
// Login to FTP
ftpClient = new FTPClient();
InetAddress address = InetAddress.getByName(SettingsManager.getFTPServer());
ftpClient.connect(address);
ftpClient.login("anonymous", null);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
File localDir = new File(baseDir);
File[] localFiles = localDir.listFiles();
Map<String, Date> remoteTimestamps = getTimestamps();
// Loop through local files and send all that are more recent
if (localFiles != null) {
for (int i = 0; i < localFiles.length; i++) {
File localFile = localFiles[i];
setUpdateIndicator("Uploading " + (i+1) + "/" + localFiles.length);
if(localFile.isDirectory()) continue;
// Remove timestamts file
if(localFile.getName().equals(timestampsFilename)) continue;
// Remove meta files if the option is disabled
if(!sendMetaFiles && meta_string_array.contains(localFile.getName())) continue;
Date remoteTimestamp = remoteTimestamps.get(localFile.getName());
Date localTimeStamp = getLocalFileUtcTimestamp(localFile);
if (remoteTimestamp == null || after(localTimeStamp, remoteTimestamp)) {
uploadFile(localFile);
Log.i(getClass().toString(), "Uploaded" + localFile.getName());
setLocalFileTimestamp(localFile, curSyncTime);
remoteTimestamps.put(localFile.getName(), curSyncTime);
upCount++;
}else{
Log.i(getClass().toString(), "Did not upload");
}
}
}
Set<String> keySet = remoteTimestamps.keySet();
Iterator<String> keyIt = keySet.iterator();
for (int i = 0; i < keySet.size(); i++) {
String remoteFile = keyIt.next();
setUpdateIndicator("Downloading " + (i+1) + "/" + keySet.size());
File localFile = new File(baseDir, remoteFile);
if(remoteFile.equals(timestampsFilename)) continue;
// Remove meta files if the option is disabled
if(!sendMetaFiles && meta_string_array.contains(remoteFile)) continue;
// Date t1 = getLocalFileUtcTimestamp(localFile);
// Date t2 = getUtcTimestamp(remoteFile);
////
// System.out.println("- " + t1 + (t1.after(t2) ? ">" : "<") + t2);
Date localTimeStamp = getLocalFileUtcTimestamp(localFile);
Date remoteTimestamp = remoteTimestamps.get(remoteFile);
if (!localFile.exists() || (after(remoteTimestamp, localTimeStamp) && !localTimeStamp.equals(remoteTimestamp))) {
downloadFile(remoteFile, localFile);
Log.i(getClass().toString(), "Downloaded " + localFile.getName());
if(!localFile.exists()) Log.i(getClass().toString(), "Not exist");
else if(after(remoteTimestamp, localTimeStamp)) Log.i(getClass().toString(), "Before: " + (localTimeStamp.getTime()-remoteTimestamp.getTime()));
// Date d = getUtcTimestamp(remoteFile);
setLocalFileTimestamp(localFile, remoteTimestamps.get(localFile.getName()));
// remoteTimestamps.put(remoteFile, curSyncTime);
downCount++;
}else{
Log.i(getClass().toString(), "Did not download");
}
}
setTimestamps(remoteTimestamps);
} catch (Exception e) {
AlertManager.error("Failed Syncing!", e);
onResult.onResult(true, upCount, downCount);
setUpdateIndicator("ERROR!");
} finally {
onResult.onResult(false, upCount, downCount);
setUpdateIndicator("Finished");
}
isRunning = false;
}
private boolean setTimestamps(Map<String, Date> timestamps){
try {
ByteBuilder bb = new ByteBuilder();
String[] filenames = timestamps.keySet().toArray(new String[0]);
for(int i = 0; i < filenames.length; i++){
bb.addString(filenames[i]);
bb.addLong(timestamps.get(filenames[i]).getTime());
}
FileEditor.writeFile(timestampsFilename, bb.build());
uploadFile(new File(baseDir + timestampsFilename));
return true;
} catch (ByteBuilder.buildingException | IOException e) {
AlertManager.error("Failed Syncing!", e);
return false;
}
}
private Map<String, Date> getTimestamps() {
try {
downloadFile(timestampsFilename, new File(baseDir + timestampsFilename));
byte[] data = FileEditor.readFile(timestampsFilename);
if(data == null || data.length == 0)
return new HashMap<>();
BuiltByteParser bbp = new BuiltByteParser(data);
List<BuiltByteParser.parsedObject> pa = bbp.parse();
Map<String, Date> output = new HashMap<>();
for(int i = 0; i < pa.size(); i+=2){
// System.out.println((long) pa.get(i).get());
output.put(
(String) pa.get(i).get(),
new Date((long) pa.get(i+1).get())
);
}
return output;
}catch (IOException | BuiltByteParser.byteParsingExeption e){
AlertManager.error("Failed Syncing!", e);
return new HashMap<>();
}
}
}
@@ -24,6 +24,7 @@ import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -49,7 +50,10 @@ public class FileSelectorFragment extends Fragment {
meta_string_array = new String[]{
"matches.fields",
"pits.fields",
evcode+".eventdata"
evcode+".eventdata",
evcode+".rescout",
evcode+".scoutnotice",
"todelete.colabarray",
};
String[] files = FileEditor.getEventFiles(evcode);
@@ -74,10 +78,12 @@ public class FileSelectorFragment extends Fragment {
checkBox.setChecked(true);
tr.addView(checkBox);
TextView tv = new TextView(getContext());
tv.setText(String.valueOf(files[i]));
tv.setTextSize(20);
tr.addView(tv);
// Filename
tr.addView(
new TextViewBuilder(getContext(), files[i])
.size(20)
.build()
);
final int fi = i;
tr.setOnClickListener(view -> {
@@ -4,14 +4,14 @@ import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir;
import android.util.Log;
import com.ridgebotics.ridgescout.types.ColabArray;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.HttpGetFile;
import com.ridgebotics.ridgescout.utility.HttpPutFile;
import com.ridgebotics.ridgescout.utility.RequestTask;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.ToDelete;
import org.json.JSONException;
import org.json.JSONObject;
@@ -23,10 +23,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -100,6 +98,10 @@ public class HttpSync extends Thread {
public void run() {
isRunning = true;
boolean sendMetaFiles = SettingsManager.getFTPSendMetaFiles();
ToDelete.reload_todelete_list();
List<String> removeFiles = ToDelete.todelete_list.get();
String serverIP = SettingsManager.getFTPServer();
String serverKey = SettingsManager.getFTPKey();
@@ -117,6 +119,9 @@ public class HttpSync extends Thread {
getLocalFileMetadata();
localFiles.removeIf(localFile -> removeFiles.contains(localFile.filename+","+localFile.checksum));
remoteFiles.removeIf(remoteFile -> removeFiles.contains(remoteFile.filename+","+remoteFile.checksum));
@@ -129,23 +134,30 @@ public class HttpSync extends Thread {
TransferFile remoteFile = findInFileArray(remoteFiles, localFile.filename);
// Check if the file is a meta file, and uploads it based off of the setting
boolean sendField = (sendMetaFiles || !(localFile.filename.endsWith(".fields")));
boolean shouldUpload;
boolean special;
if(
(
sendMetaFiles || !(
localFile.filename.endsWith(".fields")
)
// If there is no file on the sever, upload.
if(remoteFile == null) {
shouldUpload = true;
special = false;
}
else {
// If the remote file is the same as the local one, do nothing.
boolean checksumsEqual = Objects.equals(localFile.checksum, remoteFile.checksum);
// If the local file is a colabarray, give it a special propreties
special = FileEditor.requiresSpecialInteraction(remoteFile.filename);
// If the local file is updated after the remote file
boolean after = after(localFile.updated, remoteFile.updated);
)
&& (remoteFile == null ||
(
!Objects.equals(localFile.checksum, remoteFile.checksum) &&
after(localFile.updated, remoteFile.updated)
)
)) {
uploadFile(localFile, serverIP, serverKey);
// await();
shouldUpload = !checksumsEqual && (special || after);
}
if(sendField && shouldUpload) {
uploadFile(localFile, serverIP, serverKey, special);
Log.d(getClass().toString(), "LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": Uploaded");
upCount++;
}else {
@@ -162,13 +174,23 @@ public class HttpSync extends Thread {
TransferFile localFile = findInFileArray(localFiles, remoteFile.filename);
if(localFile == null ||
(
!Objects.equals(localFile.checksum, remoteFile.checksum) &&
after(remoteFile.updated, localFile.updated) &&
!localFile.updated.equals(remoteFile.updated)
)
) {
boolean shouldUpload;
// If there is no file on the sever, upload.
if(localFile == null) {
shouldUpload = true;
} else {
// If the remote file is the same as the local one, do nothing.
boolean checksumsEqual = !Objects.equals(localFile.checksum, remoteFile.checksum);
// If the local file is updated after the remote file
boolean after = after(remoteFile.updated, localFile.updated);
// If the local file and remote file's upload dates are exactly the same
boolean datesEqual = !localFile.updated.equals(remoteFile.updated);
shouldUpload = (!checksumsEqual && (after) && !datesEqual);
}
if(shouldUpload) {
downloadFile(remoteFile, serverIP);
// await();
Log.d(getClass().toString(), "RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": Downloaded");
@@ -181,8 +203,8 @@ public class HttpSync extends Thread {
setUpdateIndicator("Downloading " + (Math.floor((double) (i * 1000) / remoteFiles.size()) / 10) + "%");
}
// Remove files marked for deletion
ToDelete.deleteFiles();
setUpdateIndicator("Finished, " + upCount + " Up, " + downCount + " Down");
@@ -192,6 +214,7 @@ public class HttpSync extends Thread {
}
// Find file based off of filename
private TransferFile findInFileArray(List<TransferFile> files, String filename){
for(TransferFile file : files) {
if(file.filename.equals(filename))
@@ -200,29 +223,12 @@ public class HttpSync extends Thread {
return null;
}
// Get teh last modified date of a file
private Date getLocalFileUtcTimestamp(File file) {
return new Date(file.lastModified());
}
public static String getSHA256Hash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
FileInputStream fis = new FileInputStream(filePath);
byte[] byteArray = new byte[1024];
int bytesCount = 0;
while ((bytesCount = fis.read(byteArray)) != -1) {
digest.update(byteArray, 0, bytesCount);
}
fis.close();
byte[] bytes = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
// Load the local metadata of files
private void getLocalFileMetadata() {
File localDir = new File(baseDir);
File[] localFileNames = localDir.listFiles();
@@ -239,9 +245,10 @@ public class HttpSync extends Thread {
tf.filename = file.getName();
tf.updated = getLocalFileUtcTimestamp(file);
try {
tf.checksum = getSHA256Hash(file.getPath());
tf.checksum = FileEditor.getSHA256Hash(file.getName());
} catch (Exception e) {
AlertManager.error("Failed to get hash of: " + file.getName(), e);
continue;
}
localFiles.add(tf);
}
@@ -277,90 +284,100 @@ public class HttpSync extends Thread {
await();
}
// private boolean setTimestamps(Map<String, Date> timestamps){
// try {
// ByteBuilder bb = new ByteBuilder();
// String[] filenames = timestamps.keySet().toArray(new String[0]);
//
// for(int i = 0; i < filenames.length; i++){
// bb.addString(filenames[i]);
// bb.addLong(timestamps.get(filenames[i]).getTime());
// }
//
// FileEditor.writeFile(timestampsFilename, bb.build());
//
// uploadFile(new File(baseDir + timestampsFilename));
// return true;
// } catch (ByteBuilder.buildingException | IOException e) {
// AlertManager.error("Failed Syncing!", e);
// return false;
// }
// }
//
// private Map<String, Date> getTimestamps() {
// try {
// downloadFile(timestampsFilename, new File(baseDir + timestampsFilename));
//
// byte[] data = FileEditor.readFile(timestampsFilename);
//
// if(data == null || data.length == 0)
// return new HashMap<>();
//
// BuiltByteParser bbp = new BuiltByteParser(data);
// List<BuiltByteParser.parsedObject> pa = bbp.parse();
//
// Map<String, Date> output = new HashMap<>();
// for(int i = 0; i < pa.size(); i+=2){
//// System.out.println((long) pa.get(i).get());
// output.put(
// (String) pa.get(i).get(),
// new Date((long) pa.get(i+1).get())
// );
// }
// return output;
//
// }catch (IOException | BuiltByteParser.byteParsingExeption e){
// AlertManager.error("Failed Syncing!", e);
// return new HashMap<>();
// }
// }
void uploadFile(TransferFile tf, String serverURL, String apiKey) {
// Create HTTP request to upload file
void uploadFile(TransferFile tf, String serverURL, String apiKey, boolean special) {
runningRequest.set(false);
HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), new HttpPutFile.UploadCallback() {
@Override
public void onResult(String error) {
if(error != null)
// If the file is "special", download the server copy and merge the local and remote ColabArrays
if(special) {
HttpGetFile getTask = new HttpGetFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), (stream, error) -> {
if(error != null) {
AlertManager.error(error);
return;
} else if (stream == null) {
AlertManager.error("Output stream from download was null!");
return;
}
byte[] bytes = stream.toByteArray();
FileEditor.syncColabArray(
tf.filename,
FileEditor.readFile(tf.filename),
bytes
);
HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), error2 -> {
if (error2 != null)
AlertManager.error(error2);
runningRequest.set(true);
}, new String[]{
"api_key: " + apiKey,
("modified: " + tf.updated.getTime())
});
uploadTask.execute();
});
getTask.execute();
} else {
// Upload the file
HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), error -> {
if (error != null)
AlertManager.error(error);
runningRequest.set(true);
}
}, new String[]{
"api_key: " + apiKey,
("modified: " + tf.updated.getTime())
}); // Pass auth token if needed
}, new String[]{
"api_key: " + apiKey,
("modified: " + tf.updated.getTime())
}); // Pass auth token if needed
uploadTask.execute();
await();
uploadTask.execute();
await();
}
}
private void setLocalFileTimestamp(File file, Date date) {
file.setLastModified(date.getTime());
}
// Download a file from the remote server
void downloadFile(TransferFile tf, String serverURL) {
runningRequest.set(false);
File f = new File(baseDir + tf.filename);
HttpGetFile uploadTask = new HttpGetFile(serverURL + "/api/" + tf.filename, f, new HttpGetFile.DownloadCallback() {
@Override
public void onResult(String error) {
if(error != null)
AlertManager.error(error);
else
setLocalFileTimestamp(f, tf.updated);
runningRequest.set(true);
HttpGetFile uploadTask = new HttpGetFile(serverURL + "/api/" + tf.filename, f, (stream, error) -> {
if(error != null) {
AlertManager.error(error);
return;
} else if (stream == null) {
AlertManager.error("Output stream from download was null!");
return;
}
}); // Pass auth token if needed
byte[] bytes = stream.toByteArray();
if(FileEditor.requiresSpecialInteraction(tf.filename)) {
FileEditor.syncColabArray(
tf.filename,
FileEditor.readFile(tf.filename),
bytes
);
} else {
FileEditor.writeFile(tf.filename, bytes);
}
setLocalFileTimestamp(f, tf.updated);
runningRequest.set(true);
});
uploadTask.execute();
await();
@@ -8,7 +8,11 @@ import static com.ridgebotics.ridgescout.utility.FileEditor.TBAAddress;
import static com.ridgebotics.ridgescout.utility.FileEditor.TBAHeader;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +38,7 @@ import com.ridgebotics.ridgescout.utility.JSONUtil;
import com.ridgebotics.ridgescout.utility.RequestTask;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import org.json.JSONArray;
import org.json.JSONException;
@@ -41,6 +46,7 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;
// Class to download data from a specific event and encode it.
public class TBAEventFragment extends Fragment {
@@ -71,23 +77,14 @@ public class TBAEventFragment extends Fragment {
Table = binding.matchTable;
Table.setStretchAllColumns(true);
AlertManager.startLoading("Loading Teams and Matches...");
Table.removeAllViews();
// Table.removeAllViews();
Table.setStretchAllColumns(true);
Table.bringToFront();
TableRow tr1 = new TableRow(getContext());
addTableText(tr1, "Downloading Teams...");
Table.addView(tr1);
final RequestTask rq = new RequestTask();
rq.onResult(teamsStr -> {
TableRow tr11 = new TableRow(getContext());
addTableText(tr11, "Downloading Matches...");
Table.addView(tr11);
final RequestTask rq1 = new RequestTask();
rq1.onResult(matchesStr -> {
matchTable(matchesStr, teamsStr, eventData);
@@ -103,18 +100,13 @@ public class TBAEventFragment extends Fragment {
}
private void addTableText(TableRow tr, String textStr){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center
text.setText(textStr);
tr.addView(text);
tr.addView(new TextViewBuilder(getContext(), textStr)
.size(18)
// .align_center()
.build());
}
public void matchTable(String matchesString, String teamsString, JSONObject eventData){
Table.removeAllViews();
Table.setStretchAllColumns(true);
Table.bringToFront();
try {
final JSONArray matchData = new JSONArray(matchesString);
// final JSONArray matchData = new JSONArray();
@@ -129,27 +121,16 @@ public class TBAEventFragment extends Fragment {
}
// Event code at top
TextView tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setText(matchKey);
tv.setTextSize(18);
Table.addView(tv);
Table.addView(new TextViewBuilder(getContext(), matchKey)
.align_center()
.size(18)
.build());
// Event Name
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(matchName);
tv.setTextSize(28);
Table.addView(tv);
Table.addView(new TextViewBuilder(getContext(), matchName)
.align_center()
.size(28)
.build());
// Save button
MaterialButton btn = new MaterialButton(getContext());
@@ -164,68 +145,42 @@ public class TBAEventFragment extends Fragment {
// If there are no matches, add the error.
// If there are no teams, don't allow the user to save the event and set the button to be invisible
if(teamData.length() == 0){
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("This event has no teams released yet...");
tv.setTextSize(18);
Table.addView(tv);
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("This event has no teams released yet...");
tv.setTextSize(18);
Table.addView(tv);
Table.addView(new TextViewBuilder(getContext(), "This event has no teams released yet...")
.align_center()
.size(18)
.build());
btn.setVisibility(View.GONE);
return;
}else if(matchData.length() == 0){
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("This event has no matches released yet...");
tv.setTextSize(18);
Table.addView(tv);
Table.addView(new TextViewBuilder(getContext(), "This event has no matches released yet...")
.align_center()
.size(18)
.build());
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("Try manually adding practice matches.");
tv.setTextSize(18);
Table.addView(tv);
Table.addView(new TextViewBuilder(getContext(), "Try manually adding practice matches.")
.align_center()
.size(18)
.build());
}
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("Teams");
tv.setTextSize(28);
Table.addView(tv);
Table.addView(
new TextViewBuilder(getContext(), "Teams")
.align_center()
.size(28)
.build()
);
// Sort the teams into numerical order
int[] teams = new int[teamData.length()];
for(int i = 0 ; i < teamData.length(); i++){
@@ -234,28 +189,26 @@ public class TBAEventFragment extends Fragment {
Arrays.sort(teams);
// Loop through each match
TableRow tr = null;
for(int i=0; i < teamData.length(); i++){
// frcTeam team = event.teams.get(i);
int num = teams[i];
// If this is every 7th row, add the new row.
if(i % 7 == 0){
if(i != 0)
Table.addView(tr);
tr = new TableRow(getContext());
}
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
text.setText(String.valueOf(num));
// if(fileEditor.fileExist(event.eventCode + "-" + num + ".pitscoutdata")){
// text.setBackgroundColor(0x3000FF00);
// }else{
// text.setBackgroundColor(0x30FF0000);
// }
tr.addView(text);
tr.addView(
new TextViewBuilder(getContext(), String.valueOf(num))
.align_center()
.size(18)
.build()
);
}
if(tr != null)
Table.addView(tr);
@@ -268,19 +221,13 @@ public class TBAEventFragment extends Fragment {
tv = new TextView(getContext());
tv.setLayoutParams(new TableRow.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("Matches");
tv.setTextSize(28);
Table.addView(tv);
Table.addView(
new TextViewBuilder(getContext(), "Matches")
.align_center()
.size(28)
.build()
);
tr = new TableRow(getContext());
addTableText(tr, "#");
addTableText(tr, "Red-1");
@@ -334,22 +281,24 @@ public class TBAEventFragment extends Fragment {
int[] redKeys = new int[3];
for(int b=0;b<6;b++){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center
tr.addView(text);
TextViewBuilder text = new TextViewBuilder(getContext())
.size(18)
.align_center();
if(b < 3){
String str = redAlliance.getString(b).substring(3);
redKeys[b] = Integer.parseInt(str);
text.setText(str);
text.setBackgroundColor(tba_red);
text.text(str);
text.tv.setBackgroundColor(tba_red);
}else{
String str = blueAlliance.getString(b-3).substring(3);
blueKeys[b-3] = Integer.parseInt(str);
text.setText(str);
text.setBackgroundColor(tba_blue);
text.text(str);
text.tv.setBackgroundColor(tba_blue);
}
tr.addView(text.build());
}
Table.addView(tr);
@@ -364,14 +313,6 @@ public class TBAEventFragment extends Fragment {
toggle = !toggle;
}
// btn.setOnClickListener(v -> {
// if(saveData(matchesOBJ, teamData, eventData)){
// alert("Info", "Saved!");
// }else{
// alert("Error", "Error saving files.");
// }
// });
}catch (JSONException j){
AlertManager.error("Failed Downloading", j);
AlertManager.stopLoading();
@@ -379,7 +320,7 @@ public class TBAEventFragment extends Fragment {
}
private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){
AlertManager.startLoading("Saving data...");
AlertManager.startLoading("Downloading team data...");
Thread t = new Thread(() -> {
try {
@@ -404,16 +345,44 @@ public class TBAEventFragment extends Fragment {
teamObj.country = team.getString("country");
teamObj.startingYear = team.getInt("rookie_year");
ImageRequestTask imageRequestTask = new ImageRequestTask();
imageRequestTask.onResult(bitmap -> {
teamObj.bitmap = bitmap;
teamObj.teamColor = frcTeam.findPrimaryColor(bitmap);
teams.add(teamObj);
RequestTask rq = new RequestTask();
rq.onResult(s -> {
try {
JSONArray jsonArray = new JSONArray(s);
JSONObject jsonObject = jsonArray.getJSONObject(0);
String base64 = jsonObject.getJSONObject("details").getString("base64Image");
byte[] decodedData = Base64.decode(base64, Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(decodedData, 0, decodedData.length);
// System.out.println(base64);
teamObj.bitmap = bitmap;
teamObj.teamColor = frcTeam.findPrimaryColor(bitmap);
Log.i("TBA", "Got icon for team " + teamObj.teamNumber);
} catch (Exception e){
Log.i("TBA", "Failed to icon for team " + teamObj.teamNumber);
} finally {
teams.add(teamObj);
}
return null;
});
imageRequestTask.execute("https://www.thebluealliance.com/avatar/" + year + "/frc" + teamObj.teamNumber + ".png");
rq.execute((TBAAddress + "team/frc" + teamObj.teamNumber + "/media/" + year), TBAHeader);
// ImageRequestTask imageRequestTask = new ImageRequestTask();
//
// imageRequestTask.onResult(bitmap -> {
// teamObj.bitmap = bitmap;
// teamObj.teamColor = frcTeam.findPrimaryColor(bitmap);
// teams.add(teamObj);
//
// return null;
// });
// imageRequestTask.execute("https://www.thebluealliance.com/avatar/" + year + "/frc" + teamObj.teamNumber + ".png");
}
while (teams.size() != teamData.length()) {
@@ -25,6 +25,7 @@ import com.ridgebotics.ridgescout.ui.views.TBAEventOption;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.RequestTask;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import org.json.JSONArray;
import org.json.JSONException;
@@ -54,12 +55,6 @@ public class TBASelectorFragment extends Fragment {
Table = binding.matchTable;
Table.setStretchAllColumns(true);
TableRow tr = new TableRow(getContext());
addTableText(tr, "Loading Events...");
Table.addView(tr);
startLoading("Loading Events...");
final RequestTask rq = new RequestTask();
@@ -77,14 +72,6 @@ public class TBASelectorFragment extends Fragment {
return binding.getRoot();
}
private void addTableText(TableRow tr, String textStr){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center
text.setText(textStr);
tr.addView(text);
}
public static int getEventTypeWeight(String type){
switch(type){
case "Preseason": return -3;
@@ -103,7 +90,7 @@ public class TBASelectorFragment extends Fragment {
public void eventTable(String dataString){
Table.removeAllViews();
Table.setStretchAllColumns(true);
// Table.setStretchAllColumns(true);
Table.bringToFront();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
@@ -13,6 +13,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.input.FieldposType;
import com.ridgebotics.ridgescout.utility.SettingsManager;
// Custom view to display a field position.
@@ -24,6 +25,8 @@ public class FieldPosView extends FrameLayout {
private ImageView imageView;
private boolean enabled = true;
private boolean flip = false;
public interface onTapListener {
void onUpdate(int[] pos);
};
@@ -65,24 +68,21 @@ public class FieldPosView extends FrameLayout {
// Set touch listener
setOnTouchListener((v, event) -> {
if (enabled && event.getAction() == MotionEvent.ACTION_DOWN) {
x = (int) ((event.getX()/getWidth())*255);
y = (int) ((event.getY()/getHeight())*255);
// If the field image is rotated, rotate the input and output
if(!flip) {
x = (int) (event.getX() / getWidth()) * 255;
y = (int) (event.getY() / getHeight()) * 255;
} else {
x = (int) (1 - (event.getX() / getWidth())) * 255;
y = (int) (1 - (event.getY() / getHeight())) * 255;
}
System.out.println("X: " + x + ", Y: " + y);
tl.onUpdate(getPos());
invalidate();
return true;
}
return false;
});
switch(SettingsManager.getFieldImageIndex()){
case "2025":
setImageResource(R.drawable.field_2025);
break;
case "2025 (Flipped)":
setImageResource(R.drawable.field_2025_flipped);
break;
}
}
public void setPos(int[] pos){
@@ -96,10 +96,28 @@ public class FieldPosView extends FrameLayout {
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (x >= 0 && y >= 0) {
canvas.drawCircle(
((float) x /255)*getWidth(),
((float) y /255)*getHeight(),
POINT_RADIUS, paint);
float cx;
float cy;
// If the field image is rotated, rotate the input and output
if(!flip) {
cx = ((float) x / 255)*getWidth();
cy = ((float) y / 255)*getHeight();
} else {
cx = (1 - (float) x / 255)*getWidth();
cy = (1 - (float) y /255)*getHeight();
}
canvas.drawCircle(cx, cy, POINT_RADIUS, paint);
}
}
public void setFieldImage(FieldposType.FieldImage image) {
if(image.flippable && SettingsManager.getFieldImageFlipped()) {
setImageResource(image.resId_flipped);
flip = true;
} else {
setImageResource(image.resId_normal);
}
}
@@ -10,6 +10,8 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.input.FieldposType;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import java.util.ArrayList;
import java.util.List;
@@ -48,9 +50,6 @@ public class MultiFieldPosView extends FrameLayout {
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setAdjustViewBounds(true);
addView(imageView);
setImageResource(R.drawable.field_2025);
}
public void addPos(int[] pos){
@@ -62,6 +61,14 @@ public class MultiFieldPosView extends FrameLayout {
invalidate();
}
public void setFieldImage(FieldposType.FieldImage image) {
if(image.flippable && SettingsManager.getFieldImageFlipped()) {
setImageResource(image.resId_flipped);
} else {
setImageResource(image.resId_normal);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -1,105 +0,0 @@
package com.ridgebotics.ridgescout.ui.views;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class RecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerHolder<T>> {
private List<T> items;
private final int layoutResId;
private final RecyclerHolderFactory<T> viewHolderFactory;
private RecyclerClickListener<T> onItemClickListener;
public RecyclerAdapter(int layoutResId, RecyclerHolderFactory<T> viewHolderFactory) {
this.items = new ArrayList<>();
this.layoutResId = layoutResId;
this.viewHolderFactory = viewHolderFactory;
}
@Override
public RecyclerHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(layoutResId, parent, false);
return viewHolderFactory.createViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerHolder<T> holder, int position) {
T item = items.get(position);
holder.bind(item, position);
holder.setOnItemClickListener(item, onItemClickListener);
}
@Override
public int getItemCount() {
return items.size();
}
// List management methods
public void setItems(List<T> newItems) {
this.items.clear();
if (newItems != null) {
this.items.addAll(newItems);
}
notifyDataSetChanged();
}
public void addItem(T item) {
items.add(item);
notifyItemInserted(items.size() - 1);
}
public void addItem(int position, T item) {
items.add(position, item);
notifyItemInserted(position);
}
public void removeItem(int position) {
if (position >= 0 && position < items.size()) {
items.remove(position);
notifyItemRemoved(position);
}
}
public void removeItem(T item) {
int position = items.indexOf(item);
if (position != -1) {
removeItem(position);
}
}
public void updateItem(int position, T item) {
if (position >= 0 && position < items.size()) {
items.set(position, item);
notifyItemChanged(position);
}
}
public void clear() {
int size = items.size();
items.clear();
notifyItemRangeRemoved(0, size);
}
public T getItem(int position) {
return items.get(position);
}
public List<T> getItems() {
return new ArrayList<>(items);
}
public void setOnItemClickListener(RecyclerClickListener<T> listener) {
this.onItemClickListener = listener;
}
}
@@ -1,5 +0,0 @@
package com.ridgebotics.ridgescout.ui.views;
public interface RecyclerClickListener<T> {
void onItemClick(T item, int position);
}
@@ -1,19 +0,0 @@
package com.ridgebotics.ridgescout.ui.views;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
public abstract class RecyclerHolder<T> extends RecyclerView.ViewHolder {
public RecyclerHolder(View itemView) {
super(itemView);
}
public abstract void bind(T item, int position);
// Optional method for handling item clicks
public void setOnItemClickListener(T item, RecyclerClickListener<T> listener) {
if (listener != null) {
itemView.setOnClickListener(v -> listener.onItemClick(item, getAdapterPosition()));
}
}
}
@@ -1,7 +0,0 @@
package com.ridgebotics.ridgescout.ui.views;
import android.view.View;
public interface RecyclerHolderFactory<T> {
RecyclerHolder<T> createViewHolder(View itemView);
}
@@ -1,133 +0,0 @@
package com.ridgebotics.ridgescout.ui.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import java.util.List;
public class RecyclerList<T> extends RecyclerView {
private RecyclerAdapter<T> adapter;
public RecyclerList(Context context) {
super(context);
init();
}
public RecyclerList(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RecyclerList(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
// Set default layout manager
setLayoutManager(new LinearLayoutManager(getContext()));
// Enable optimizations
setHasFixedSize(true);
setItemViewCacheSize(20);
setDrawingCacheEnabled(true);
setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
}
// Setup method to configure the RecyclerView
public RecyclerList<T> setup(int layoutResId, RecyclerHolderFactory<T> RecyclerHolderFactory) {
adapter = new RecyclerAdapter<>(layoutResId, RecyclerHolderFactory);
setAdapter(adapter);
return this;
}
// Layout manager convenience methods
public RecyclerList<T> withLinearLayout() {
setLayoutManager(new LinearLayoutManager(getContext()));
return this;
}
public RecyclerList<T> withLinearLayout(int orientation) {
setLayoutManager(new LinearLayoutManager(getContext(), orientation, false));
return this;
}
public RecyclerList<T> withGridLayout(int spanCount) {
setLayoutManager(new GridLayoutManager(getContext(), spanCount));
return this;
}
public RecyclerList<T> withDivider() {
DividerItemDecoration divider = new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL);
addItemDecoration(divider);
return this;
}
public RecyclerList<T> withItemClickListener(RecyclerClickListener<T> listener) {
if (adapter != null) {
adapter.setOnItemClickListener(listener);
}
return this;
}
// Data management methods
public void setItems(List<T> items) {
if (adapter != null) {
adapter.setItems(items);
}
}
public void addItem(T item) {
if (adapter != null) {
adapter.addItem(item);
}
}
public void addItem(int position, T item) {
if (adapter != null) {
adapter.addItem(position, item);
}
}
public void removeItem(int position) {
if (adapter != null) {
adapter.removeItem(position);
}
}
public void removeItem(T item) {
if (adapter != null) {
adapter.removeItem(item);
}
}
public void updateItem(int position, T item) {
if (adapter != null) {
adapter.updateItem(position, item);
}
}
public void clear() {
if (adapter != null) {
adapter.clear();
}
}
public T getItem(int position) {
return adapter != null ? adapter.getItem(position) : null;
}
public List<T> getItems() {
return adapter != null ? adapter.getItems() : null;
}
public RecyclerAdapter<T> getGenericAdapter() {
return adapter;
}
}
@@ -19,37 +19,34 @@ import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.frcTeam;
// A view that acts as a row specifically to display a team and their icon in a list formmt.
public class TeamListOption extends RecyclerHolder<frcTeam> {
public class TeamListOption extends LinearLayout {
public TeamListOption(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TeamListOption(Context context) {
super(context);
init(context);
}
// public TeamListOption(Context context, @Nullable AttributeSet attrs) {
// super(context, attrs);
// init(context);
// }
//
// public TeamListOption(Context context) {
// super(context);
// init(context);
// }
//
private TextView teamNumber;
private TextView teamName;
private ImageView teamLogo;
private ConstraintLayout box;
private View coloredBackground;
//
public TeamListOption(View view) {
super(view);
// LayoutInflater.from(context).inflate(R.layout.view_team_option, this, true);
teamNumber = view.findViewById(R.id.field_option_type);
teamName = view.findViewById(R.id.field_option_name);
teamLogo = view.findViewById(R.id.team_option_logo);
public void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_team_option, this, true);
teamNumber = findViewById(R.id.field_option_type);
teamName = findViewById(R.id.field_option_name);
teamLogo = findViewById(R.id.team_option_logo);
box = view.findViewById(R.id.team_option_box);
coloredBackground = view.findViewById(R.id.team_option_background);
box = findViewById(R.id.team_option_box);
coloredBackground = findViewById(R.id.team_option_background);
}
public void setTeamNumber(int num){
@@ -62,7 +59,6 @@ public class TeamListOption extends RecyclerHolder<frcTeam> {
public void setTeamLogo(Bitmap bitmap){
teamLogo.setImageBitmap(bitmap);
showLogo();
}
public void hideLogo(){
@@ -72,8 +68,7 @@ public class TeamListOption extends RecyclerHolder<frcTeam> {
teamLogo.setVisibility(View.VISIBLE);
}
@Override
public void bind(frcTeam team, int position){
public void fromTeam(frcTeam team){
setTeamNumber(team.teamNumber);
setTeamName(team.teamName);
@@ -2,13 +2,10 @@ package com.ridgebotics.ridgescout.utility;
import com.ridgebotics.ridgescout.scoutingData.Fields;
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
import com.ridgebotics.ridgescout.types.ColabArray;
import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.types.input.FieldType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// Static class to hold loaded data, for ease of access.
public class DataManager {
public static String evcode;
@@ -62,37 +59,39 @@ public class DataManager {
}
}
public static List<String> rescout_list = new ArrayList<>();
public static ColabArray rescout_list = new ColabArray();
public static void reload_rescout_list(){
if(!FileEditor.fileExist(evcode + ".rescout")) {rescout_list = new ArrayList<>(); return;}
byte[] file = FileEditor.readFile(evcode + ".rescout");
if(file == null) {rescout_list = new ArrayList<>(); return;}
String filename = evcode + ".rescout";
if(!FileEditor.fileExist(filename)) {rescout_list = new ColabArray(); return;}
byte[] file = FileEditor.readFile(filename);
if(file == null) {rescout_list = new ColabArray(); return;}
try {
BuiltByteParser bbp = new BuiltByteParser(file);
rescout_list = new ArrayList<>(Arrays.asList((String[]) (bbp.parse().get(0).get())));
rescout_list = ColabArray.decode(file);
} catch (Exception e){
AlertManager.error("Error loading scout fields", e);
rescout_list = new ArrayList<>();
AlertManager.error("Error loading rescouting list", e);
rescout_list = new ColabArray();
}
}
public static void save_rescout_list() {
String filename = evcode + ".rescout";
try {
if(rescout_list.size() == 0){
FileEditor.deleteFile(evcode + ".rescout");
return;
}
ByteBuilder bb = new ByteBuilder();
bb.addStringArray(rescout_list.toArray(new String[0]));
FileEditor.writeFile(evcode + ".rescout", bb.build());
FileEditor.writeFile(filename, rescout_list.encode());
} catch (Exception e){
AlertManager.error("Error saving scout fields", e);
AlertManager.error("Error saving rescouting list", e);
}
}
public static String scoutNotice = "";
public static void reload_scout_notice(){
@@ -106,7 +105,7 @@ public class DataManager {
} catch (Exception e){
AlertManager.error("Error loading scout notice", e);
rescout_list = new ArrayList<>();
scoutNotice = "";
}
}
@@ -1,7 +1,15 @@
package com.ridgebotics.ridgescout.utility;
import static com.ridgebotics.ridgescout.utility.DataManager.match_transferValues;
import static com.ridgebotics.ridgescout.utility.DataManager.match_values;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_values;
import android.annotation.SuppressLint;
import android.content.Context;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
import com.ridgebotics.ridgescout.types.ColabArray;
import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.types.frcTeam;
@@ -15,6 +23,8 @@ import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -27,18 +37,17 @@ import java.util.zip.Inflater;
// Helper class for binary editing
public final class FileEditor {
@SuppressLint("SdCardPath")
public final static String baseDir = "/data/data/com.ridgebotics.ridgescout/";
public static final byte internalDataVersion = 0x01;
public static final int maxCompressedBlockSize = 4096;
public static final int lengthHeaderBytes = 3;
public static final String TBAAddress = "https://www.thebluealliance.com/api/v3/";
// Hardcoded API key go brrr
public static final String TBAHeader = "X-TBA-Auth-Key: tjEKSZojAU2pgbs2mBt06SKyOakVhLutj3NwuxLTxPKQPLih11aCIwRIVFXKzY4e";
// private TimeZone localTimeZone = TimeZone.getDefault();
public static String binaryVisualize(byte[] bytes){
String returnStr = "";
@@ -233,16 +242,19 @@ public final class FileEditor {
// }
public static boolean writeFile(String filepath, byte[] data) {
return writeFile(new File(baseDir + filepath), data);
}
public static boolean writeFile(File file, byte[] data) {
try {
FileOutputStream output = new FileOutputStream(baseDir + filepath);
FileOutputStream output = new FileOutputStream(file.getPath());
output.write(data);
output.close();
// Date d = new Date();
new File(baseDir + filepath).setLastModified(new Date().getTime());
file.setLastModified(new Date().getTime());
return true;
}
catch (IOException e) {
@@ -279,10 +291,15 @@ public final class FileEditor {
}
public static byte[] readFile(String path){
return readFileExact(baseDir + path);
return readFileExact(new File(baseDir + path));
}
public static byte[] readFileExact(String path){
File file = new File(path);
public static byte[] readFile(File path){
return readFileExact(path);
}
public static byte[] readFileExact(File file){
int size = (int) file.length();
byte[] bytes = new byte[size];
try {
@@ -290,9 +307,6 @@ public final class FileEditor {
buf.read(bytes, 0, bytes.length);
buf.close();
return bytes;
} catch (FileNotFoundException e) {
AlertManager.error(e);
return null;
} catch (IOException e) {
AlertManager.error(e);
return null;
@@ -314,6 +328,29 @@ public final class FileEditor {
public static String getSHA256Hash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
FileInputStream fis = new FileInputStream(baseDir + filePath);
byte[] byteArray = new byte[1024];
int bytesCount = 0;
while ((bytesCount = fis.read(byteArray)) != -1) {
digest.update(byteArray, 0, bytesCount);
}
fis.close();
byte[] bytes = digest.digest();
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static boolean setEvent(frcEvent event){
@@ -373,25 +410,21 @@ public final class FileEditor {
public static String[] getEventFiles(String evcode){
public static String[] getFiles(){
File f = new File(baseDir);
File[] files = f.listFiles();
if(files == null){return new String[0];}
ArrayList<String> outFiles = new ArrayList<>();
outFiles.add("matches.fields");
outFiles.add("pits.fields");
// outFiles.add(evcode + ".eventdata");
List<String> outFiles = new ArrayList<>();
for (File file : files) {
String name = file.getName();
if(!file.isDirectory() && name.startsWith(evcode)) {
if (!file.isDirectory()) {
outFiles.add(file.getName());
}
}
String[] filenames = outFiles.toArray(new String[0]);
try {
@@ -413,6 +446,24 @@ public final class FileEditor {
return filenames;
}
public static String[] getEventFiles(String evcode){
String[] files = getFiles();
List<String> outFiles = new ArrayList<>();
outFiles.add("matches.fields");
outFiles.add("pits.fields");
for (String file : files) {
if(file.startsWith(evcode)) {
outFiles.add(file);
}
}
return outFiles.toArray(new String[0]);
}
// https://stackoverflow.com/questions/7620401/how-to-convert-image-file-data-in-a-byte-array-to-a-bitmap
// public static String imageToBitMap(byte[] data) throws IOException {
// Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -425,5 +476,72 @@ public final class FileEditor {
public static boolean setTeams(Context context, String key, ArrayList<frcTeam> teams){
return true;
}
public static List<String> findCorruptedFiles() {
List<String> removeFiles = new ArrayList<>();
String[] localFiles = FileEditor.getFiles();
DataManager.reload_match_fields();
DataManager.reload_pit_fields();
for(int i = 0; i < localFiles.length; i++){
String filename = localFiles[i];
String[] split = filename.split("\\.");
String extention =split[split.length-1];
try {
switch (extention) {
case "matchscoutdata":
ScoutingDataWriter.load(filename, match_values, match_transferValues);
break;
case "pitscoutdata":
ScoutingDataWriter.load(filename, pit_values, pit_transferValues);
break;
default:
continue;
}
} catch (Exception e) {
removeFiles.add(filename);
}
}
return removeFiles;
}
public static boolean requiresSpecialInteraction(String name) {
// String name = file.getName();
if(!fileExist(name)) {
return false;
}
if(name.endsWith(".rescout")) {
return true;
}
return false;
}
public static void syncColabArray(String filename, byte[] currentBytes, byte[] newBytes) {
if(!fileExist(filename)) {
return;
}
try{
if(filename.endsWith(".rescout")) {
ColabArray colabArrayCurrent = ColabArray.decode(currentBytes);
ColabArray colabArrayNew = ColabArray.decode(newBytes);
colabArrayCurrent.append(colabArrayNew);
writeFile(filename, colabArrayCurrent.encode());
}
} catch (Exception e) {
AlertManager.error("Failed to sync ColabArray!", e);
}
}
}
@@ -5,14 +5,16 @@ import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
// Class to download remote file.
public class HttpGetFile extends AsyncTask<Void, Integer, File> {
public interface DownloadCallback {
void onResult(String error);
void onResult(ByteArrayOutputStream bytes, String error);
}
private String downloadUrl;
private File destinationFile;
private ByteArrayOutputStream outputStream;
private DownloadCallback callback;
private String errorMessage;
public HttpGetFile(String downloadUrl, File destinationFile, DownloadCallback callback) {
@@ -23,9 +25,12 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
@Override
protected File doInBackground(Void... voids) {
return run();
}
public File run() {
HttpURLConnection connection = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
URL url = new URL(downloadUrl);
@@ -64,7 +69,7 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
}
}
outputStream = new FileOutputStream(destinationFile);
outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
long downloadedBytes = 0;
@@ -87,6 +92,8 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
}
outputStream.flush();
// FileEditor.writeFile(destinationFile, outputStream.toByteArray());
// Log.d(TAG, "Download successful. File saved to: " + destinationFile.getAbsolutePath());
return destinationFile;
@@ -104,7 +111,7 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
@Override
protected void onPostExecute(File result) {
if (callback != null) {
callback.onResult(errorMessage);
callback.onResult(outputStream, errorMessage);
}
}
@@ -112,7 +119,7 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
protected void onCancelled() {
deletePartialFile();
if (callback != null) {
callback.onResult("Download cancelled");
callback.onResult(null, "Download cancelled");
}
}
@@ -130,8 +137,7 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
return response.toString();
}
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error reading error response", e);
AlertManager.error("Error reading error response", e);
}
return null;
}
@@ -139,9 +145,9 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
private void deletePartialFile() {
if (destinationFile != null && destinationFile.exists()) {
if (destinationFile.delete()) {
// Log.d(TAG, "Partial download file deleted");
AlertManager.error("Partial download file deleted");
} else {
// Log.w(TAG, "Failed to delete partial download file");
AlertManager.error("Failed to delete partial download file");
}
}
}
@@ -150,15 +156,13 @@ public class HttpGetFile extends AsyncTask<Void, Integer, File> {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error closing input stream", e);
AlertManager.error("Error closing input stream", e);
}
try {
if (outputStream != null) outputStream.close();
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error closing output stream", e);
AlertManager.error("Error closing output stream", e);
}
if (connection != null) {
@@ -1,11 +1,14 @@
package com.ridgebotics.ridgescout.utility;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
//import android.util.Log;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
// Class to send HTTP PUT request to upload file
public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
// private static final String TAG = "FileUploadTask";
@@ -27,8 +30,13 @@ public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
this.headers = headers;
}
@SuppressLint("WrongThread")
@Override
protected Boolean doInBackground(Void... voids) {
return run();
}
public boolean run() {
HttpURLConnection connection = null;
InputStream fileInputStream = null;
OutputStream outputStream = null;
@@ -49,8 +57,8 @@ public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/octet-stream");
connection.setRequestProperty("Content-Length", String.valueOf(fileToUpload.length()));
connection.setConnectTimeout(30000); // 30 seconds
connection.setReadTimeout(60000); // 60 seconds
connection.setConnectTimeout(5000); // 5 seconds
connection.setReadTimeout(10000); // 10 seconds
for(int i = 0; i < headers.length; i++){
String[] split = headers[i].split(": ");
@@ -95,8 +103,8 @@ public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
}
} catch (Exception e) {
AlertManager.error(e);
errorMessage = "Upload error: " + e.getMessage();
AlertManager.error(errorMessage, e);
// Log.e(TAG, errorMessage, e);
return false;
} finally {
@@ -133,25 +141,23 @@ public class HttpPutFile extends AsyncTask<Void, Integer, Boolean> {
return response.toString();
}
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error reading error response", e);
AlertManager.error("Error reading error response", e);
}
return null;
}
// Clean up stream
private void closeResources(InputStream inputStream, OutputStream outputStream, HttpURLConnection connection) {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error closing input stream", e);
AlertManager.error("Error closing input stream", e);
}
try {
if (outputStream != null) outputStream.close();
} catch (IOException e) {
AlertManager.error(e);
// Log.e(TAG, "Error closing output stream", e);
AlertManager.error("Error closing output stream", e);
}
if (connection != null) {
@@ -17,7 +17,7 @@ public class SettingsManager {
public static final String SelEVCodeKey = "selected_event_code";
public static final String YearNumKey = "year_num";
public static final String FieldImageKey = "field_image";
public static final String FieldImageKey = "field_image_flipped";
public static final String MatchNumKey = "match_num";
public static final String AllyPosKey = "alliance_pos";
@@ -47,7 +47,7 @@ public class SettingsManager {
hm.put(SelEVCodeKey, "unset");
hm.put(WifiModeKey, false);
hm.put(YearNumKey, 2025);
hm.put(FieldImageKey, "2025");
hm.put(FieldImageKey, false);
hm.put(MatchNumKey, 0);
hm.put(AllyPosKey, "red-1");
hm.put(DataModeKey, 0);
@@ -77,7 +77,7 @@ public class SettingsManager {
getEditor().putBoolean(WifiModeKey, (boolean) defaults.get( WifiModeKey )).apply();
getEditor() .putInt(YearNumKey, (int) defaults.get( YearNumKey )).apply();
getEditor() .putString(FieldImageKey, (String) defaults.get( FieldImageKey )).apply();
getEditor() .putBoolean(FieldImageKey, (boolean) defaults.get( FieldImageKey )).apply();
getEditor() .putInt(MatchNumKey, (int) defaults.get( MatchNumKey )).apply();
getEditor() .putString(AllyPosKey, (String) defaults.get( AllyPosKey )).apply();
getEditor() .putInt(DataModeKey, (int) defaults.get( DataModeKey )).apply();
@@ -108,8 +108,8 @@ public class SettingsManager {
public static int getYearNum(){return prefs.getInt( YearNumKey, (int) defaults.get(YearNumKey));}
public static void setYearNum(int num){ getEditor().putInt( YearNumKey,num).apply();}
public static String getFieldImageIndex(){return prefs.getString( FieldImageKey, (String) defaults.get(FieldImageKey));}
public static void setFieldImageIndex(String str){ getEditor().putString( FieldImageKey,str).apply();}
public static boolean getFieldImageFlipped(){return prefs.getBoolean(FieldImageKey, (boolean) defaults.get(FieldImageKey));}
public static void setFieldImageFlipped(boolean bool){ getEditor().putBoolean(FieldImageKey, bool).apply();}
public static int getMatchNum(){return prefs.getInt( MatchNumKey, (int) defaults.get(MatchNumKey));}
public static void setMatchNum(int num){ getEditor().putInt( MatchNumKey,num).apply();}
@@ -0,0 +1,150 @@
package com.ridgebotics.ridgescout.utility;
import static android.widget.LinearLayout.VERTICAL;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import com.ridgebotics.ridgescout.types.ColabArray;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.List;
public class ToDelete {
public static final String filename = "todelete.colabarray";
public static void findCorruptedFiles(Context c) {
new Thread(() -> {
AlertManager.startLoading("Loading files...");
List<String> filenames = FileEditor.findCorruptedFiles();
AlertManager.stopLoading();
((Activity) c).runOnUiThread(() -> {
deleteFiles(c, filenames, true);
});
}).start();
}
public static void deleteFiles(Context c, List<String> files, boolean defaultOption) {
ScrollView sv = new ScrollView(c);
LinearLayout ll = new LinearLayout(c);
ll.setOrientation(VERTICAL);
sv.addView(ll);
CheckBox[] checkboxes = new CheckBox[files.size()];
for(int i =0; i < files.size(); i++){
CheckBox cb = new CheckBox(c);
cb.setText(files.get(i));
cb.setChecked(defaultOption);
ll.addView(cb);
checkboxes[i] = cb;
}
AlertDialog.Builder alert = new AlertDialog.Builder(c);
alert.setTitle("Delete files");
alert.setView(sv);
alert.setNeutralButton("Cancel", null);
alert.setPositiveButton("Delete", (_dialogInterface, _i) -> {
List<String> delete_files = new ArrayList<>();
for(int i = 0; i < files.size(); i++) {
if(checkboxes[i].isChecked())
delete_files.add(files.get(i));
}
AlertDialog.Builder confirm = new AlertDialog.Builder(c);
alert.setTitle("Confirm");
alert.setView(new TextViewBuilder(c, "Are you sure you want to delete " + delete_files.size() + " files?").build());
alert.setNeutralButton("Cancel", null);
alert.setPositiveButton("Delete", (dialogInterface, i) -> {
deleteFiles(delete_files);
});
alert.setCancelable(false);
alert.create().show();
});
alert.setCancelable(false);
alert.create().show();
}
public static ColabArray todelete_list = new ColabArray();
public static void reload_todelete_list(){
if(!FileEditor.fileExist(ToDelete.filename)) {todelete_list = new ColabArray(); return;}
byte[] file = FileEditor.readFile(ToDelete.filename);
if(file == null) {todelete_list = new ColabArray(); return;}
try {
todelete_list = ColabArray.decode(file);
} catch (Exception e){
AlertManager.error("Error loading todelete list", e);
todelete_list = new ColabArray();
}
}
public static void save_todelete_list() {
try {
FileEditor.writeFile(ToDelete.filename, todelete_list.encode());
} catch (Exception e){
AlertManager.error("Error saving todelete list", e);
}
}
private static void deleteFiles(List<String> toDelete) {
reload_todelete_list();
for(String file : toDelete) {
String hash;
try {
hash = FileEditor.getSHA256Hash(file);
} catch (Exception e) {
AlertManager.error("Failed to get hash of file: " + file, e);
continue;
}
todelete_list.add(file+","+hash);
FileEditor.deleteFile(file);
}
save_todelete_list();
}
public static void deleteFiles() {
reload_todelete_list();
List<String> toDelete = todelete_list.get();
for(String filename : FileEditor.getFiles()){
try {
String hash = FileEditor.getSHA256Hash(filename);
if(toDelete.contains(filename+","+hash)) {
FileEditor.deleteFile(filename);
}
} catch (Exception e) {
AlertManager.error("Failed to get hash of file: " + filename, e);
continue;
}
}
}
public static boolean contains(String localfile) {
try {
String hash = FileEditor.getSHA256Hash(localfile);
return contains(localfile, hash);
} catch (Exception e) {
AlertManager.error("Failed to get hash of file: " + localfile, e);
return false;
}
}
public static boolean contains(String filename, String hash){
return todelete_list.contains(filename+","+hash);
}
}
@@ -0,0 +1,161 @@
package com.ridgebotics.ridgescout.utility.builders;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TableRow;
import android.widget.TextView;
public class TextViewBuilder {
public TextView tv;
public TextViewBuilder(Context c) {
tv = new TextView(c);
}
public TextViewBuilder(Context c, String str) {
tv = new TextView(c);
tv.setText(str);
}
public TextViewBuilder size(float size) {
tv.setTextSize(size);
return this;
}
public TextViewBuilder text(String str) {
tv.setText(str);
return this;
}
public TextViewBuilder padding(int borders) {
tv.setPadding(borders,borders,borders,borders);
return this;
}
public TextViewBuilder padding(int horisontal, int vertical) {
tv.setPadding(horisontal,vertical,horisontal,vertical);
return this;
}
public TextViewBuilder padding(int left, int right, int top, int bottom) {
tv.setPadding(left,top,right,bottom);
return this;
}
public TextViewBuilder align_left() {
tv.setGravity(Gravity.START);
tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
return this;
}
public TextViewBuilder align_center() {
tv.setGravity(Gravity.CENTER_HORIZONTAL);
// FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.WRAP_CONTENT
// );
// params.gravity = Gravity.CENTER;
// tv.setLayoutParams(params);
//
//
tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
return this;
}
// public TextViewBuilder center_xy() {
// TableRow.LayoutParams params = new TableRow.LayoutParams();
// params.gravity = Gravity.CENTER;
// tv.setLayoutParams(params);
// return this;
// }
public TextViewBuilder align_right() {
tv.setGravity(Gravity.END);
tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END);
return this;
}
public TextViewBuilder layout_wrap_wrap() {
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return this;
}
public TextViewBuilder layout_wrap_match() {
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
return this;
}
public TextViewBuilder layout_match_wrap() {
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return this;
}
public TextViewBuilder layout_match_match() {
tv.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
return this;
}
public TextViewBuilder h1() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline1);
return this;
}
public TextViewBuilder h2() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline2);
return this;
}
public TextViewBuilder h3() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline3);
return this;
}
public TextViewBuilder h4() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline4);
return this;
}
public TextViewBuilder h5() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5);
return this;
}
public TextViewBuilder h6() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
return this;
}
public TextViewBuilder sub1() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
return this;
}
public TextViewBuilder sub2() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle2);
return this;
}
public TextViewBuilder body1() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
return this;
}
public TextViewBuilder body2() {
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body2);
return this;
}
public TextView build() {
return tv;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+25 -12
View File
@@ -17,23 +17,36 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/table"
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.ridgebotics.ridgescout.ui.views.CustomSpinnerView
android:id="@+id/data_type_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="50dp"
android:orientation="vertical">
<TableLayout
android:id="@+id/table"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.ridgebotics.ridgescout.ui.views.CustomSpinnerView
android:id="@+id/data_type_dropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</TableLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -29,7 +29,31 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="0dp" />
tools:layout_editor_absoluteX="0dp" >
</com.ridgebotics.ridgescout.ui.views.TeamCard>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="@+id/tbaButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:layout_weight="1"
android:text="View on TBA" />
<Button
android:id="@+id/statboticsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:layout_weight="1"
android:text="View on Statbotics" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
+1 -3
View File
@@ -30,13 +30,11 @@
android:layout_height="54dp"
android:layout_margin="2dp"
android:scaleType="fitXY"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_robologo"
tools:visibility="visible" />
tools:srcCompat="@drawable/ic_robologo" />
<TextView
android:id="@+id/field_option_name"
+1 -5
View File
@@ -1,6 +1,5 @@
[versions]
agp = "8.11.1"
asynclayoutinflator = "1.1.0"
agp = "8.13.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
@@ -11,13 +10,11 @@ lifecycleLivedataKtx = "2.6.1"
lifecycleViewmodelKtx = "2.6.1"
material3 = "1.3.1"
navigationFragment = "2.6.0"
navigationFragmentVersion = "2.8.9"
navigationUi = "2.6.0"
supportAnnotations = "28.0.0"
preference = "1.2.1"
[libraries]
asynclayoutinflator = { module = "androidx.asynclayoutinflator:asynclayoutinflator", version.ref = "asynclayoutinflator" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@@ -28,7 +25,6 @@ lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-lived
lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
navigation-fragment-v289 = { module = "androidx.navigation:navigation-fragment", version.ref = "navigationFragmentVersion" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" }
support-annotations = { group = "com.android.support", name = "support-annotations", version.ref = "supportAnnotations" }
preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }