18 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
Michael Mikovsky 05a40e39c3 Stuff 2025-07-27 21:57:33 -06:00
Michael Mikovsky 090a0579b9 Settings buttons are now defined programmatically 2025-07-27 19:15:46 -06:00
Michael Mikovsky a30664000c Reformat reciever 2025-07-27 16:28:10 -06:00
Michael Mikovsky 1bae273abd Fix bugs related to qr code scanning. Fixes #13 2025-07-27 16:27:31 -06:00
Michael Mikovsky 5c2b1fef2e Remove asynctask from codescannertask 2025-07-27 12:58:03 -06:00
Michael Mikovsky dbba56e649 Fix code scanning sliders and remove asynctask 2025-07-27 12:57:35 -06:00
Michael Mikovsky 4aa31b620d Settings page improvements 2025-07-25 13:54:36 -06:00
Michael Mikovsky 890b879ef9 Solve random things 2025-07-25 12:51:01 -06:00
62 changed files with 2410 additions and 1474 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
### 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)|
|![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)|
+6 -6
View File
@@ -44,8 +44,8 @@ android {
buildFeatures {
viewBinding = true
}
aaptOptions {
noCompress("tflite");
androidResources {
noCompress += listOf("tflite")
}
}
@@ -66,10 +66,10 @@ dependencies {
androidTestImplementation(libs.espresso.core)
var camerax_version = "1.3.2"
implementation("androidx.camera:camera-core:1.3.2")
implementation("androidx.camera:camera-camera2:1.3.2")
implementation("androidx.camera:camera-lifecycle:1.3.2")
var camerax_version = "1.4.2"
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:${camerax_version}")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
@@ -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
@@ -0,0 +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();
}
}
@@ -9,14 +9,13 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.SettingsManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
// Class to contain data for an entire event.
// Easily encoded and decoded to binary format.
public class frcEvent {
public static final int typecode = 254;
public String eventCode;
public String name;
public ArrayList<frcMatch> matches;
@@ -134,29 +133,29 @@ public class frcEvent {
return null;
}
// public
// Returns the soonest match that there will be all the possible upcoming data on other teams
public void getReportMatches(int ourTeamNum){
frcMatch[] teamMatches = event.getTeamMatches(ourTeamNum);
public int getMostInformedBy(int ourTeamNum, int curMatch){
frcMatch teamMatch = getNextTeamMatch(ourTeamNum, curMatch);
for(int i = 0; i < teamMatches.length; i++){
int maxMatch = -1;
for(int a = 0; a < 6; a++){
int teamNum;
if(a < 3)
teamNum = teamMatches[i].redAlliance[a];
else
teamNum = teamMatches[i].blueAlliance[a-3];
int maxMatch = Integer.MIN_VALUE;
if(teamNum == ourTeamNum)
continue;
for(int a = 0; a < 6; a++){
int teamNum;
if(a < 3)
teamNum = teamMatch.redAlliance[a];
else
teamNum = teamMatch.blueAlliance[a-3];
int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex);
if(maxMatch < matchNum)
maxMatch = matchNum;
}
if(teamNum == ourTeamNum)
continue;
int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatch.matchIndex);
if(maxMatch < matchNum)
maxMatch = matchNum;
}
return maxMatch;
}
public frcTeam getTeamByNum(int teamNum){
@@ -168,6 +167,26 @@ public class frcEvent {
return null;
}
public List<frcTeam> getTeamsSorted() {
int[] teamNums = new int[teams.size()];
for(int i = 0 ; i < teams.size(); i++){
teamNums[i] = teams.get(i).teamNumber;
}
Arrays.sort(teamNums);
List<frcTeam> list = new ArrayList<>();
for(int i = 0; i < teams.size(); i++){
frcTeam team = getTeamByNum(teamNums[i]);
assert team != null;
list.add(team);
}
return list;
}
public boolean getIsBlueAlliance(int teamNum, int matchNum){
return getIsBlueAlliance(teamNum, matches.get(matchNum));
@@ -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));
});
}
}
@@ -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;
@@ -20,8 +20,10 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
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;
@@ -76,7 +78,6 @@ public class PitScoutingFragment extends Fragment {
String fileUsernames = "";
ToggleTitleView[] titles;
AutoSaveManager asm = new AutoSaveManager(this::save, AUTO_SAVE_DELAY);
ArrayList<RawDataType> rawDataTypes;
@@ -99,7 +100,7 @@ public class PitScoutingFragment extends Fragment {
}
public void set_indicator_color(int color){
binding.pitFileIndicator.setBackgroundColor(color);
binding.pitIndicator.setColor(color);
}
public void update_asm(){
@@ -114,10 +115,9 @@ public class PitScoutingFragment extends Fragment {
public void loadTeam(){
// clear_fields();
binding.pitFileIndicator.setVisibility(View.VISIBLE);
binding.pitsTeamCard.setVisibility(View.VISIBLE);
binding.pitBarTeamNum.setText(String.valueOf(team.teamNumber));
binding.pitUsername.setText(SettingsManager.getUsername());
binding.pitIndicator.setTeamNum(team.teamNumber);
binding.pitIndicator.setUsername(SettingsManager.getUsername());
binding.pitsTeamCard.fromTeam(team);
filename = evcode + "-" + team.teamNumber + ".pitscoutdata";
@@ -146,7 +146,7 @@ public class PitScoutingFragment extends Fragment {
}
}
binding.pitFileIndicator.bringToFront();
binding.pitIndicator.bringToFront();
asm.start();
@@ -154,7 +154,7 @@ public class PitScoutingFragment extends Fragment {
private void enableRescoutButton(){
set_indicator_color(rescout ? rescout_color : saved_color);
binding.pitFileIndicator.setOnLongClickListener(v -> {
binding.pitIndicator.setOnLongClickListener(v -> {
rescout = !rescout;
if(rescout){
set_indicator_color(rescout_color);
@@ -171,7 +171,7 @@ public class PitScoutingFragment extends Fragment {
}
private void disableRescoutButton(){
binding.pitFileIndicator.setOnLongClickListener(null);
binding.pitIndicator.setOnLongClickListener(null);
}
@@ -225,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;
@@ -15,6 +15,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -27,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;
@@ -89,7 +91,7 @@ public class ScoutingFragment extends Fragment {
binding.textMatchAlliance.setVisibility(View.GONE);
binding.textName.setVisibility(View.GONE);
binding.textNextMatch.setVisibility(View.GONE);
binding.infoBox.setVisibility(View.GONE);
binding.textRescoutIndicator.setVisibility(View.GONE);
binding.matchScoutingButton.setEnabled(false);
@@ -123,20 +125,7 @@ public class ScoutingFragment extends Fragment {
findNavController(this).navigate(R.id.action_navigation_scouting_to_navigation_scouting_event);
});
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
int matchNum = SettingsManager.getMatchNum();
int nextMatch = -1;
try {
nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex;
} catch (Exception e){
AlertManager.error(e);
}
binding.textNextMatch.setText("Our next match: Match " + nextMatch);
binding.textMatchAlliance.setText("Match: " + (matchNum+1) + ", " + SettingsManager.getAllyPos());
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
updateDashboard();
return binding.getRoot();
}
@@ -165,4 +154,31 @@ public class ScoutingFragment extends Fragment {
});
}
private void updateDashboard() {
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
int curMatchNum = SettingsManager.getMatchNum();
int nextMatch;
int teamNum = SettingsManager.getTeamNum();
try {
nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex;
} catch (Exception e){
AlertManager.error("Sorry, in event ("+evcode+"), your team number ("+teamNum+") wasn't found!", e);
return;
}
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch)
.body1()
.build());
int informedBy = event.getMostInformedBy(teamNum, curMatchNum);
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]);
@@ -21,6 +21,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import com.google.android.material.button.MaterialButton;
import com.ridgebotics.ridgescout.MainActivity;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.databinding.FragmentSettingsFieldsBinding;
@@ -29,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;
@@ -195,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);
@@ -216,7 +217,7 @@ public class FieldsFragment extends Fragment {
AlertDialog dialog = alert.create();
dialog.show();
Button deleteButton = new Button(getContext());
MaterialButton deleteButton = new MaterialButton(getContext());
deleteButton.setText("DELETE");
deleteButton.setOnClickListener(l -> {
AlertDialog.Builder alert2 = new AlertDialog.Builder(getContext());
@@ -1,6 +1,7 @@
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;
import static com.ridgebotics.ridgescout.utility.SettingsManager.AllyPosKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.CustomEventsKey;
@@ -13,11 +14,15 @@ import static com.ridgebotics.ridgescout.utility.SettingsManager.UnameKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.WifiModeKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.YearNumKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.defaults;
import static com.ridgebotics.ridgescout.utility.SettingsManager.getEVCode;
import static com.ridgebotics.ridgescout.utility.SettingsManager.getEditor;
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;
@@ -26,7 +31,6 @@ 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;
@@ -36,7 +40,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.divider.MaterialDivider;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.ridgebotics.ridgescout.R;
@@ -44,9 +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 com.ridgebotics.ridgescout.utility.ToDelete;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -65,20 +73,6 @@ public class SettingsFragment extends Fragment {
reloadSettings();
binding.fieldsButton.setOnClickListener(v -> {
binding.fieldsButton.setEnabled(false);
binding.fieldsButtons.setVisibility(VISIBLE);
});
binding.fieldsMatchesButton.setOnClickListener(v -> {
FieldsFragment.set_filename(Fields.matchFieldsFilename);
findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
});
binding.fieldsPitsButton.setOnClickListener(v -> {
FieldsFragment.set_filename(Fields.pitsFieldsFilename);
findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
});
return root;
@@ -91,27 +85,73 @@ 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("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"));
ButtonSettingsItem fieldsButtons = new ButtonSettingsItem();
fieldsButtons.addButton("Edit pit fields", v -> {
FieldsFragment.set_filename(Fields.pitsFieldsFilename);
findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
});
fieldsButtons.addButton("Edit match fields", v -> {
FieldsFragment.set_filename(Fields.matchFieldsFilename);
findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
});
manager.addItem(fieldsButtons);
ButtonSettingsItem noticeButton = new ButtonSettingsItem();
noticeButton.addButton("Edit scout notice", v->editNotice());
noticeButton.setEnabled(!getEVCode().equals("unset"));
manager.addItem(noticeButton);
manager.addItem(new CheckboxSettingsItem(CustomEventsKey, "Custom Events"));
CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "[⚠] Send meta files");
manager.addItem(FTPSendMetaFiles);
manager.addItem(new HeaderSettingsItem("Admin"));
StringSettingsItem FTPKey = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPKey, "Sync Key");
manager.addItem(FTPKey);
StringSettingsItem FTPServer = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPServer, "Sync Server (Sync)");
manager.addItem(FTPServer);
CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "⚠ Send meta files");
manager.addItem(FTPSendMetaFiles);
CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "FTP Enabled", FTPServer, FTPKey, FTPSendMetaFiles);
CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "Sync Enabled", FTPServer, FTPKey, FTPSendMetaFiles);
manager.addItem(FTPEnabled);
manager.addItem(new CheckboxSettingsItem(WifiModeKey, "Wifi Mode", FTPEnabled));
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 NumberSettingsItem(YearNumKey, "Year", 0, 9999));
manager.addItem(new HeaderSettingsItem("Connection"));
manager.addItem(new CheckboxSettingsItem(EnableQuickAllianceChangeKey, "Enable quick alliance swap", null));
manager.addItem(new CheckboxSettingsItem(FieldImageKey, "Field Image", null));
manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list));
@@ -134,16 +174,48 @@ public class SettingsFragment extends Fragment {
manager.addItem(new StringSettingsItem(UnameKey, "Username"));
manager.addItem(new NumberSettingsItem(TeamNumKey, "Team Number", 0, 99999));
manager.addItem(new HeaderSettingsItem("Scouting"));
binding.SettingsTable.removeAllViews();
manager.getView(binding.SettingsTable);
if(!DataManager.getevcode().equals("unset")){
Button editNoticeButton = new Button(getContext());
editNoticeButton.setText("Edit Scout Notice");
binding.SettingsTable.addView(editNoticeButton);
editNoticeButton.setOnClickListener(v->editNotice());
}
// Add "Edit scout notice" button to the bottom of the page=
// Add "Field edit" buttons to the bottom of the page
// LinearLayout fieldButtons = new LinearLayout(getContext());
// fieldButtons.setLayoutParams(new LinearLayout.LayoutParams(
// LinearLayout.LayoutParams.MATCH_PARENT,
// LinearLayout.LayoutParams.WRAP_CONTENT
// ));
// fieldButtons.setOrientation(HORIZONTAL);
//
// Button editMatchFieldsButton = new Button(getContext());
// editMatchFieldsButton.setText("Edit Match Fields");
// editMatchFieldsButton.setOnClickListener(v -> {
// FieldsFragment.set_filename(Fields.matchFieldsFilename);
// findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
// });
// fieldButtons.addView(editMatchFieldsButton);
//
//
// Button editPitsFieldsButton = new Button(getContext());
// editPitsFieldsButton.setText("Edit pits Fields");
// editPitsFieldsButton.setOnClickListener(v -> {
// FieldsFragment.set_filename(Fields.pitsFieldsFilename);
// findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields);
// });
// fieldButtons.addView(editPitsFieldsButton);
// binding.SettingsTable.addView(fieldButtons);
}
@@ -178,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();
}
@@ -284,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);
@@ -347,7 +453,7 @@ public class SettingsFragment extends Fragment {
@Override
public View createView(Context context) {
LinearLayout ll = new LinearLayout(getContext());
ll.setOrientation(LinearLayout.VERTICAL);
ll.setOrientation(VERTICAL);
tally = new TallyCounterView(getContext());
@@ -366,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);
@@ -475,6 +580,93 @@ public class SettingsFragment extends Fragment {
}
}
public class HeaderSettingsItem extends SettingsItem<Void> {
String title;
public HeaderSettingsItem(String title) {
super("", title, null);
this.title = title;
}
@Override
public void setEnabled(boolean enabled){
}
@Override
public View createView(Context context) {
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(VERTICAL);
ll.setPadding(0, 20,0,0);
ll.addView(new TextViewBuilder(context, title)
.h4()
.build());
ll.addView(new MaterialDivider(context));
return ll;
}
@Override
public Void getValue() {
return null;
}
}
public class ButtonSettingsItem extends SettingsItem<Void> {
List<MaterialButton> buttons = new ArrayList<>();
List<String> titles = new ArrayList<>();
List<View.OnClickListener> callbacks = new ArrayList<>();
boolean enabled = true;
public ButtonSettingsItem() {
super("", "", null);
}
@Override
public void setEnabled(boolean enabled){
this.enabled = enabled;
for(int i = 0; i < buttons.size(); i++){
buttons.get(i).setEnabled(enabled);
}
}
public void addButton(String text, View.OnClickListener onClickListener) {
titles.add(text);
callbacks.add(onClickListener);
}
@Override
public View createView(Context context) {
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(HORIZONTAL);
for(int i = 0; i < titles.size(); i++){
MaterialButton button = new MaterialButton(context);
button.setText(titles.get(i));
button.setOnClickListener(callbacks.get(i));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
layoutParams.setMargins(3, 0, 3, 0);
// layoutParams.weight
button.setLayoutParams(layoutParams);
button.setEnabled(enabled);
// button.weight
buttons.add(button);
ll.addView(button);
}
return ll;
}
@Override
public Void getValue() {
return null;
}
}
public class SettingsManager {
private Context context;
private HashMap<String, Object> settings;
@@ -497,7 +689,7 @@ public class SettingsFragment extends Fragment {
items.add(item);
LinearLayout itemContainer = new LinearLayout(context);
itemContainer.setOrientation(LinearLayout.VERTICAL);
itemContainer.setOrientation(VERTICAL);
itemContainer.setPadding(32, 0, 32, 8);
View view = item.createView(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
// 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,15 +23,12 @@ 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;
// This is now deprsicated
// Class to syncronise data over FTP.
// Class to synchronise data over HTTP.
public class HttpSync extends Thread {
public static final String timestampsFilename = "timestamps";
@@ -101,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();
@@ -118,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));
@@ -130,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 {
@@ -163,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");
@@ -182,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");
@@ -193,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))
@@ -201,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();
@@ -240,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);
}
@@ -278,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;
@@ -22,6 +26,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.databinding.FragmentTransferTbaBinding;
import com.ridgebotics.ridgescout.types.frcEvent;
@@ -33,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;
@@ -40,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 {
@@ -49,8 +56,6 @@ public class TBAEventFragment extends Fragment {
private final int year = SettingsManager.getYearNum();
private ProgressDialog loadingDialog;
private static JSONObject eventData = null;
public static void setEventData(JSONObject j){
eventData = j;
@@ -72,27 +77,18 @@ public class TBAEventFragment extends Fragment {
Table = binding.matchTable;
Table.setStretchAllColumns(true);
AlertManager.startLoading("Loading Teams and Matches...");
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);
stopLoading();
AlertManager.stopLoading();
return null;
});
rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader);
@@ -104,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();
@@ -130,30 +121,19 @@ 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
Button btn = new Button(getContext());
MaterialButton btn = new MaterialButton(getContext());
btn.setText("Save");
btn.setTextSize(18);
btn.setLayoutParams(new TableRow.LayoutParams(
@@ -165,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++){
@@ -235,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);
@@ -269,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");
@@ -335,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);
@@ -365,22 +313,14 @@ 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);
stopLoading();
AlertManager.stopLoading();
}
}
private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){
startLoading("Saving data...");
AlertManager.startLoading("Downloading team data...");
Thread t = new Thread(() -> {
try {
@@ -405,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()) {
@@ -431,31 +399,15 @@ public class TBAEventFragment extends Fragment {
AlertManager.toast("Saved!");
getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer));
stopLoading();
AlertManager.stopLoading();
}catch(Exception j) {
AlertManager.error(j);
stopLoading();
AlertManager.stopLoading();
}
});
t.start();
return false;
}
private void startLoading(String title){
getActivity().runOnUiThread(() -> {
if(loadingDialog != null && loadingDialog.isShowing())
loadingDialog.dismiss();
loadingDialog = ProgressDialog.show(getActivity(), title, "Please wait...");
});
}
private void stopLoading(){
getActivity().runOnUiThread(() -> {
if (loadingDialog != null)
loadingDialog.cancel();
loadingDialog = null;
});
}
}
@@ -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");
@@ -0,0 +1,135 @@
package com.ridgebotics.ridgescout.ui.transfer.codes;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
public class CodeGenTask implements Callable<List<Bitmap>> {
// private Function<List<Bitmap>, String> resultFunction = null;
//
// @Override
// protected List<Bitmap> doInBackground(String... strings) {
//
//
// return new ArrayList<>();
// }
//
//
// public void onResult(Function<List<Bitmap>, String> func) {
// this.resultFunction = func;
// }
//
//
// @Override
// protected void onPostExecute(List<Bitmap> result) {
// super.onPostExecute(result);
// if(resultFunction != null){
// resultFunction.apply(result);
// }
// }
private final String data;
private final int randID;
private final int qrSize;
private final int qrCount;
private final int imageSize;
public CodeGenTask(String data, int randID, int qrSize, int qrCount, int imageSize) {
this.data = data;
this.randID = randID;
this.qrSize = qrSize;
this.qrCount = qrCount;
this.imageSize = imageSize;
}
@Override
public List<Bitmap> call() {
List<Bitmap> qrBitmaps = new ArrayList<>();
for(int i=0;i<=((data.length()+1)/qrSize);i++){
final int start = i*qrSize;
int end = (i+1)*qrSize;
if(end >= data.length()){
end = data.length();
}
try {
Bitmap unscaledBitmap = generateQrCode(
FileEditor.byteToChar(FileEditor.internalDataVersion, FileEditor.lengthHeaderBytes) +
String.valueOf(FileEditor.byteToChar(randID, FileEditor.lengthHeaderBytes)) +
FileEditor.byteToChar(i, FileEditor.lengthHeaderBytes) +
FileEditor.byteToChar(qrCount - 1, FileEditor.lengthHeaderBytes) +
data.substring(start, end)
);
if(unscaledBitmap == null) {
AlertManager.error("Generated image was null!");
continue;
}
qrBitmaps.add(Bitmap.createScaledBitmap(unscaledBitmap, imageSize, imageSize, false));
// alert("title", ""+(qrCount-1));
}catch (WriterException e){
AlertManager.error(e);
}
}
return qrBitmaps;
}
private Bitmap generateQrCode(String contents) throws WriterException {
final int size = 512;
if (contents == null) {
return null;
}
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
// The Charset must be UTF-8, Or data will not be transferred properly. IDK why.
hints.put(EncodeHintType.CHARACTER_SET, "ISO-8859-1");
// hints.put(EncodeHintType.);
hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix result;
try {
result = writer.encode(contents, BarcodeFormat.DATA_MATRIX, size, size, hints);
} catch (IllegalArgumentException e) {
// Unsupported format
AlertManager.error(e);
return null;
}
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
}
@@ -1,9 +1,10 @@
package com.ridgebotics.ridgescout.ui.transfer.codes;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -18,16 +19,11 @@ import androidx.fragment.app.Fragment;
import com.ridgebotics.ridgescout.databinding.FragmentTransferCodeSenderBinding;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Map;
import java.util.List;
import java.util.Random;
// Class to show the code transfer thing.
@@ -38,23 +34,28 @@ public class CodeGeneratorView extends Fragment {
private TextView qrIndexN;
private TextView qrIndexD;
private final int maxQrCount = 256; //The max number that can be stored in a byte
private static final int maxQrCount = 256; //The max number that can be stored in a byte
private final int maxQrSpeed = 5;
private final int minQrSpeed = 300 + maxQrSpeed - 1;
private static final int maxQrSize = 800;
private static final int maxQrSpeed = 50;
private static final int minQrSpeed = 1000;
private static final int defaultQrDelay = 12;
private int imageSize;
private int minQrSize = 0;
private final int maxQrSize = 800;
private int qrSize = 200;
private final int defaultQrDelay = 419;
private int qrDelay = 0;
private double qrDelay = 0;
private int qrIndex = 0;
private CountDownTimer timer;
private int qrCount = 0;
private ArrayList<Bitmap> qrBitmaps;
private List<Bitmap> qrBitmaps = new ArrayList<>();
private FragmentTransferCodeSenderBinding binding;
@@ -77,6 +78,12 @@ public class CodeGeneratorView extends Fragment {
qrIndexN = binding.qrIndexN;
qrIndexD = binding.qrIndexD;
DisplayMetrics displaymetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
// int height = displaymetrics.heightPixels;
imageSize = displaymetrics.widthPixels;
// = 800;
String compressed = new String(FileEditor.blockCompress(data, FileEditor.lengthHeaderBytes), StandardCharsets.ISO_8859_1);
if(compressed.isEmpty()){
@@ -85,75 +92,30 @@ public class CodeGeneratorView extends Fragment {
}
minQrSize = Math.round((float)compressed.length() / maxQrCount)+1;
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSpeedSlider.setMax((minQrSpeed-maxQrSpeed)*2);
qrSizeSlider.setProgress(minQrSize+qrSize);
qrSpeedSlider.setProgress(defaultQrDelay+5);
qrSize += minQrSize;
sendData(compressed);
qrSpeedSlider.setMax(maxQrSpeed*2);
qrSpeedSlider.setProgress(maxQrSpeed + defaultQrDelay);
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSizeSlider.setProgress(qrSize-minQrSize);
startLoop();
return binding.getRoot();
}
private Bitmap generateQrCode(String contents) throws WriterException {
final int size = 512;
if (contents == null) {
return null;
}
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
// The Charset must be UTF-8, Or data will not be transferred properly. IDK why.
hints.put(EncodeHintType.CHARACTER_SET, "ISO-8859-1");
// hints.put(EncodeHintType.);
hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix result;
try {
result = writer.encode(contents, BarcodeFormat.DATA_MATRIX, size, size, hints);
} catch (IllegalArgumentException e) {
// Unsupported format
AlertManager.error(e);
return null;
}
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
private void sendData(String data){
qrCount = (data.length()/qrSize)+1;
qrIndexD.setText(String.valueOf(qrCount));
// alert("size", ""+binding.qrSizeSlider.getProgress()+"\n"+binding.qrSizeSlider.getMax());
qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
qrDelay = -(minQrSpeed - progress - maxQrSpeed + 1);
qrDelay = ((double) progress /maxQrSpeed) - 1;
// startLoop();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@@ -173,43 +135,24 @@ public class CodeGeneratorView extends Fragment {
qrCount = ((data.length()+1)/qrSize) +1;
qrIndexD.setText(String.valueOf(qrCount));
sendData(data);
// startLoop();
}
});
// qrSizeSlider.setProgress(qr);
AlertManager.startLoading("Generating codes...");
qrBitmaps = new ArrayList<>();
new TaskRunner().executeAsync(new CodeGenTask(data, new Random().nextInt(255), qrSize, qrCount, imageSize), result -> {
qrBitmaps = result;
AlertManager.stopLoading();
qrIndex = 0;
});
int randID = new Random().nextInt(255);
for(int i=0;i<=((data.length()+1)/qrSize);i++){
final int start = i*qrSize;
int end = (i+1)*qrSize;
if(end >= data.length()){
end = data.length();
}
try {
// alert("test", ""+Math.ceil((double)data.length()/(double)qrSize));
qrBitmaps.add(generateQrCode(
FileEditor.byteToChar(FileEditor.internalDataVersion, FileEditor.lengthHeaderBytes) +
String.valueOf(FileEditor.byteToChar(randID, FileEditor.lengthHeaderBytes)) +
FileEditor.byteToChar(i, FileEditor.lengthHeaderBytes) +
FileEditor.byteToChar(qrCount - 1, FileEditor.lengthHeaderBytes) +
data.substring(start, end)
));
// alert("title", ""+(qrCount-1));
}catch (WriterException e){
AlertManager.error(e);
}
}
qrIndex = 0;
if(timer != null){
timer.cancel();
}
qrLoop();
}
private void updateQr(){
if(qrBitmaps.isEmpty())
return;
qrImage.setImageBitmap(qrBitmaps.get(qrIndex));
if(qrDelay > 0) {
this.qrIndex += 1;
@@ -226,13 +169,36 @@ public class CodeGeneratorView extends Fragment {
qrIndexN.setText(String.valueOf(qrIndex+1));
}
private void qrLoop(){
timer = new CountDownTimer(minQrSpeed-Math.abs(qrDelay)+1, 1000) {
public void onTick(long millisUntilFinished) {}
public void onFinish() {
updateQr();
qrLoop();
private boolean shouldstop = false;
private void startLoop() {
final Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
if(shouldstop){
return;
}
try{
updateQr();
}
catch (Exception e) {
AlertManager.error(e);
}
finally{
double a = ((double) maxQrSpeed) / (Math.abs(qrDelay));
a = Math.min(Math.max(a, maxQrSpeed), minQrSpeed);
handler.postDelayed(this, (long) a);
}
}
}.start();
};
handler.post(runnable);
}
@Override
public void onDestroy() {
super.onDestroy();
shouldstop = true;
}
}
@@ -65,7 +65,7 @@ public class CodeOverlayView extends View {
}
}
if(barColors != null){
final double width = getWidth()/barColors.length;
final double width = (double) getWidth() /barColors.length;
final int top = 0;
final int bottom = barHeight;
@@ -14,19 +14,69 @@ import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.datamatrix.DataMatrixReader;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
// Background task for code scanning, to not slow down the scanner.
public class CodeScanTask extends AsyncTask<String, String, String>{
private Function<String, String> resultFunction = null;
//public class CodeScanTask extends AsyncTask<String, String, String>{
// private Function<String, String> resultFunction = null;
// private Bitmap image;
//
// @Override
// protected String doInBackground(String... str) {
// if(image == null){return null;}
//
// int width = image.getWidth();
// int height = image.getHeight();
// int[] pixels = new int[width * height];
// image.getPixels(pixels, 0, width, 0, 0, width, height);
//
// RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
// BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
//
// Map<DecodeHintType, Object> hints = new HashMap<>();
// hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
//// hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
// hints.put(DecodeHintType.POSSIBLE_FORMATS, EnumSet.of(BarcodeFormat.DATA_MATRIX));
//
// Reader reader = new DataMatrixReader();
// try {
// Result result = reader.decode(binaryBitmap, hints);
// return result.getText();
// } catch (NotFoundException | ChecksumException | FormatException e) {
//// AlertManager.error(e);
// }
//
// return null;
// }
// public void setImage(Bitmap image){this.image = image;}
// public void onResult(Function<String, String> func) {
// this.resultFunction = func;
// }
//
// @Override
// protected void onPostExecute(String result) {
// super.onPostExecute(result);
// if(resultFunction != null){
// resultFunction.apply(result);
// }
// }
//}
public class CodeScanTask implements Callable<String> {
private Bitmap image;
public CodeScanTask(Bitmap image) {
this.image = image;
}
@Override
protected String doInBackground(String... str) {
public String call() {
if(image == null){return null;}
int width = image.getWidth();
@@ -52,17 +102,4 @@ public class CodeScanTask extends AsyncTask<String, String, String>{
return null;
}
public void setImage(Bitmap image){this.image = image;}
public void onResult(Function<String, String> func) {
this.resultFunction = func;
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if(resultFunction != null){
resultFunction.apply(result);
}
}
}
}
@@ -6,6 +6,7 @@ import android.Manifest;
import android.app.AlertDialog;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.Image;
import android.os.Bundle;
import android.os.Handler;
@@ -38,11 +39,15 @@ import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.google.common.util.concurrent.ListenableFuture;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import org.checkerframework.checker.units.qual.C;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -62,21 +67,12 @@ public class CodeScannerView extends Fragment {
alert.create().show();
}
private float scale = 0;
private final int downscale = 1;
private LifecycleOwner lifecycle;
private void setImage(Bitmap bmp){
if(scale == 0) {
scale = ((float) binding.container.getWidth() / bmp.getWidth()) * ((float) 16 / 9);
binding.scannerImage.setTranslationX(0);
binding.scannerImage.setTranslationY(0);
}
scanQRCode(bmp);
binding.scannerImage.setImageBitmap(bmp);
binding.scannerThreshold.bringToFront();
// alert("test", getChildCount()+"");
}
// private Bitmap img
@@ -99,37 +95,38 @@ public class CodeScannerView extends Fragment {
final int height = image.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int L = levelMap[yBuffer.get() & 0xff];
pixels[y * width + x] = 0xff000000 | (L << 16) | (L << 8) | L;
}
for (int i = 0; i < width*height; i++) {
int L = levelMap[yBuffer.get(i) & 0xff];
pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L;
}
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
// Bitmap.rota
return bitmap;
}
public void scanQRCode(Bitmap bitmap) {
CodeScanTask async = new CodeScanTask();
async.setImage(bitmap);
async.onResult(data -> {
// CodeScanTask async = new CodeScanTask();
new TaskRunner().executeAsync(new CodeScanTask(bitmap), data -> {
if(data != null){
// alert("test", ""+fileEditor.byteFromChar(data.charAt(0)));
compileData(
FileEditor.byteFromChar(data.charAt(0)),
FileEditor.byteFromChar(data.charAt(1)),
FileEditor.byteFromChar(data.charAt(2)),
(FileEditor.byteFromChar(data.charAt(3))+1),
data.substring(4)
FileEditor.byteFromChar(data.charAt(0)),
FileEditor.byteFromChar(data.charAt(1)),
FileEditor.byteFromChar(data.charAt(2)),
(FileEditor.byteFromChar(data.charAt(3))+1),
data.substring(4)
);
}
return null;
});
async.execute();
// return contents;
@@ -231,26 +228,23 @@ public class CodeScannerView extends Fragment {
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(Surface.ROTATION_180)
.build();
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
// .addCameraFilter(CameraFilters.NON)
.build();
ExecutorService executor = Executors.newSingleThreadExecutor();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
// .setTargetResolution(new Size(224, 224))
.setOutputImageRotationEnabled(false)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
// .setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// .setOutputImageRotationEnabled(true)
// .setTargetRotation(Surface.ROTATION_0)
.build();
imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
@OptIn(markerClass = ExperimentalGetImage.class) @Override
public void analyze(@NonNull ImageProxy image) {
@@ -269,6 +263,7 @@ public class CodeScannerView extends Fragment {
});
cameraProvider.unbindAll();
// cameraProvider.ro
cameraProvider.bindToLifecycle(lifecycle,
cameraSelector, imageAnalysis, preview);
@@ -295,6 +290,7 @@ public class CodeScannerView extends Fragment {
Log.i("title", ""+qrCount);
barColors = new int[qrCount];
prevQrIndex = qrIndex;
qrScannedCount = 0;
}
final boolean updated;
@@ -313,8 +309,20 @@ public class CodeScannerView extends Fragment {
if(updated && qrScannedCount >= qrCount){
AlertManager.startLoading("Decoding data...");
new TaskRunner().executeAsync(new CodeDecodeTask(), result -> {
AlertManager.stopLoading();
});
}
prevQrIndex = qrIndex;
}
private class CodeDecodeTask implements Callable<Void> {
@Override
public Void call() {
String compiledString = "";
for(int i=0;i<qrCount;i++){
for(int i=0;i<qrDataArr.length;i++){
compiledString += qrDataArr[i];
}
@@ -342,9 +350,8 @@ public class CodeScannerView extends Fragment {
}catch (Exception e){
AlertManager.error(e);
}
return null;
}
prevQrIndex = qrIndex;
}
}
@@ -62,7 +62,7 @@ public class CustomSpinnerView extends LinearLayout {
this.index = defaultOption;
if(defaultOption != -1)
this.item.setText(options.get(defaultOption));
this.item.setText("" + options.get(defaultOption));
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
@@ -78,7 +78,7 @@ public class CustomSpinnerView extends LinearLayout {
CustomSpinnerPopup popup = new CustomSpinnerPopup(getContext()).init(options, option -> {
// dialog.();
if(!isEnabled()) return;
item.setText(option);
item.setText("" + option);
index = options.indexOf(option);
if(onClickListener != null) {
onClickListener.onClick(option, options.indexOf(option));
@@ -105,12 +105,12 @@ public class CustomSpinnerView extends LinearLayout {
}
public void setOption(String option) {
item.setText(option);
item.setText("" + option);
index = options.indexOf(option);
}
public void setOption(int index) {
item.setText(options.get(index));
item.setText("" + options.get(index));
this.index = index;
}
@@ -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);
}
}
@@ -1,6 +1,7 @@
package com.ridgebotics.ridgescout.ui.views;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
@@ -54,6 +55,20 @@ public class MatchScoutingIndicator extends RelativeLayout {
match_indicator_bar_team_num = findViewById(R.id.match_indicator_bar_team_num);
box = findViewById(R.id.file_indicator_box);
coloredBackground = findViewById(R.id.match_indicator_background);
int currentNightMode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
// Night mode is not active on device
match_indicator_back_button.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
match_indicator_next_button.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN);
break;
case Configuration.UI_MODE_NIGHT_YES:
// Night mode is active on device
match_indicator_back_button.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
match_indicator_next_button.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
break;
}
}
public void setUsername(String username){
@@ -73,19 +88,31 @@ public class MatchScoutingIndicator extends RelativeLayout {
}
public void setColor(int color){
Drawable drawable = box.getBackground();
drawable.mutate();
drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
// Set color of main background rectangle
Drawable box_drawable = box.getBackground();
box_drawable.mutate();
box_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
float[] hsv = new float[3];
Color.colorToHSV(color,hsv);
int background_color = Color.HSVToColor(220, new float[]{
hsv[0],
Math.min(hsv[1], 0.75f),
Math.min(hsv[2], 0.5f)
});
// Set color of main background rectangle, slightly dimmer
coloredBackground.setBackgroundColor(
Color.HSVToColor(220, new float[]{
hsv[0],
Math.min(hsv[1], 0.75f),
Math.min(hsv[2], 0.5f)
})
background_color
);
Drawable left_drawable = match_indicator_back_button.getBackground();
left_drawable.mutate();
left_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
Drawable right_drawable = match_indicator_next_button.getBackground();
right_drawable.mutate();
right_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
}
@@ -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);
@@ -0,0 +1,75 @@
package com.ridgebotics.ridgescout.ui.views;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.ridgebotics.ridgescout.R;
// A view for displaying information about a team.
public class PitScoutingIndicator extends RelativeLayout {
public PitScoutingIndicator(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PitScoutingIndicator(Context context) {
super(context);
init(context);
}
public TextView pit_indicator_username;
public TextView pit_indicator_team_num;
private ConstraintLayout box;
private View coloredBackground;
public void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_pit_scouting_indicator, this, true);
pit_indicator_username = findViewById(R.id.pit_indicator_username);
pit_indicator_team_num = findViewById(R.id.pit_indicator_teamnum);
box = findViewById(R.id.pit_indicator_box);
coloredBackground = findViewById(R.id.pit_indicator_background);
}
public void setUsername(String username){
pit_indicator_username.setText(username);
}
public void setTeamNum(int teamNum) {
pit_indicator_team_num.setText(String.valueOf(teamNum));
}
public void setColor(int color){
// Set color of main background rectangle
Drawable box_drawable = box.getBackground();
box_drawable.mutate();
box_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
float[] hsv = new float[3];
Color.colorToHSV(color,hsv);
int background_color = Color.HSVToColor(220, new float[]{
hsv[0],
Math.min(hsv[1], 0.75f),
Math.min(hsv[2], 0.5f)
});
// Set color of main background rectangle, slightly dimmer
coloredBackground.setBackgroundColor(
background_color
);
}
}
@@ -3,6 +3,7 @@ package com.ridgebotics.ridgescout.utility;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.Toast;
@@ -113,4 +114,22 @@ public class AlertManager {
});
}
private static ProgressDialog loadingDialog;
public static void startLoading(String title){
((Activity) context).runOnUiThread(() -> {
if(loadingDialog != null && loadingDialog.isShowing())
loadingDialog.dismiss();
loadingDialog = ProgressDialog.show(context, title, "Please wait...");
});
}
public static void stopLoading(){
((Activity) context).runOnUiThread(() -> {
if (loadingDialog != null)
loadingDialog.cancel();
loadingDialog = null;
});
}
}
@@ -6,7 +6,7 @@ import android.graphics.Color;
public class Colors {
// Lists and stuff
public static final int color_found = 0x7f00ff00;
public static final int color_rescout = 0x7f0000ff;
public static final int color_rescout = 0xff007fff;
public static final int color_not_found = 0x7f7f0000;
@@ -14,9 +14,9 @@ public class Colors {
public static final int unfocused_background_color = 0x50118811;
public static final int unsaved_color = 0x60ff0000;
public static final int saved_color = 0x6000ff00;
public static final int rescout_color = 0x600000ff;
public static final int unsaved_color = 0xffaa0000;
public static final int saved_color = 0xff00aa00;
public static final int rescout_color = 0xff007fff;
// Data graphs
@@ -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 = "";
@@ -100,7 +109,7 @@ public final class FileEditor {
public static int byteFromChar(char c){
byte[] bytes = (String.valueOf(c)).getBytes(Charset.defaultCharset());
byte[] bytes = (String.valueOf(c)).getBytes(StandardCharsets.ISO_8859_1);
return Byte.toUnsignedInt(bytes[0]);
}
@@ -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,34 +410,31 @@ 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 {
Arrays.sort(filenames, (o1, o2) -> {
try {
if (!o1.contains("-") || !o2.contains("-"))
return 0;
return Integer.valueOf(o1.split("-")[1]).compareTo(Integer.valueOf(o2.split("-")[1]));
return o2.compareTo(o1);
return Integer.valueOf(o1.split("-")[1].split("\\.")[0]).compareTo(Integer.valueOf(o2.split("-")[1].split("\\.")[0]));
} catch (Exception e) {
AlertManager.error(e);
return 0;
}
});
@@ -412,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);
@@ -424,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) {
@@ -25,11 +25,24 @@ public class ImageRequestTask extends AsyncTask<String, Void, Bitmap> {
try {
URL url = new URL(src);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// We do a little bit of spoofing
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (FileNotFoundException e) {
int code = connection.getResponseCode();
switch (code) {
case 200:
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
case 403:
// AlertManager.error("Got 403, Going to https://www.thebluealliance.com/avatars may fix this");
return null;
default:
AlertManager.error("Error downloading image " + src, "Got response code: " + code);
return null;
}
} catch (FileNotFoundException e){
return null;
} catch (IOException e){
AlertManager.error("Error downloading image " + src, e);
@@ -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,32 @@
package com.ridgebotics.ridgescout.utility;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
// https://stackoverflow.com/questions/58767733/the-asynctask-api-is-deprecated-in-android-11-what-are-the-alternatives
public class TaskRunner {
private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
private final Handler handler = new Handler(Looper.getMainLooper());
public interface Callback<R> {
void onComplete(R result);
}
public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
executor.execute(() -> {
final R result;
try {
result = callable.call();
handler.post(() -> {
callback.onComplete(result);
});
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
@@ -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

@@ -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"
+9 -11
View File
@@ -92,17 +92,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_next_match"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="TextView"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name" />
<TextView
android:id="@+id/text_match_alliance"
android:layout_width="wrap_content"
@@ -122,6 +111,15 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/event_button" />
<LinearLayout
android:id="@+id/info_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -45,7 +45,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Scouting Notice"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"></TextView>
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -4,54 +4,27 @@
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pit_file_indicator"
<com.ridgebotics.ridgescout.ui.views.PitScoutingIndicator
android:id="@+id/pit_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#60ff0000"
android:layout_height="60dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/pit_bar_team_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4388"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pitUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/pit_bar_team_num"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/pitScoutArea"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="48dp">
android:paddingTop="60dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/scouting_notice_box"
@@ -60,16 +33,15 @@
android:layout_alignParentEnd="true"
android:layout_margin="5dp"
android:background="@drawable/border"
android:padding="10dp"
android:orientation="horizontal">
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:id="@+id/scouting_notice_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:text="Scouting Notice">
</TextView>
android:text="Scouting Notice"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"></TextView>
</androidx.constraintlayout.widget.ConstraintLayout>
+56 -42
View File
@@ -10,7 +10,6 @@
android:id="@+id/scrollView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:fillViewport="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
@@ -18,55 +17,70 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TableLayout
android:id="@+id/SettingsTable"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</TableLayout>
<TableLayout
android:id="@+id/SettingsTable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<!-- <Button-->
<!-- android:id="@+id/scoutNoticeButton"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_weight="1"-->
<!-- android:layout_margin="5dp"-->
<!-- android:text="Edit Scout notice"-->
<!-- android:textSize="16sp" />-->
<!-- <LinearLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:orientation="horizontal">-->
<!-- <Button-->
<!-- android:id="@+id/fieldsPitsButton"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_weight="1"-->
<!-- android:layout_margin="5dp"-->
<!-- android:text="Edit PIT Fields"-->
<!-- android:textSize="16sp" />-->
<!-- <Button-->
<!-- android:id="@+id/fieldsMatchesButton"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_weight="1"-->
<!-- android:layout_margin="5dp"-->
<!-- android:text="Edit MATCH Fields"-->
<!-- android:textSize="16sp" />-->
<!-- </LinearLayout>-->
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/fieldsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fields"
android:textSize="34sp"
app:layout_constraintBottom_toBottomOf="@+id/scrollView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- <LinearLayout-->
<!-- android:id="@+id/fieldsButtons"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="1dp"-->
<!-- android:gravity="center"-->
<!-- android:orientation="horizontal"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintBottom_toTopOf="@+id/fieldsButton"-->
<!-- tools:visibility="visible">-->
<LinearLayout
android:id="@+id/fieldsButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:gravity="center"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/fieldsButton"
tools:visibility="visible">
<Button
android:id="@+id/fieldsPitsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Pits"
android:textSize="34sp" />
<Button
android:id="@+id/fieldsMatchesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Matches"
android:textSize="34sp" />
</LinearLayout>
<!-- </LinearLayout>-->
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -14,8 +14,7 @@
android:id="@+id/teams"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="48dp" />
android:orientation="vertical" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -7,77 +7,65 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<SeekBar
android:id="@+id/scannerColors"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="15dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<SeekBar
android:id="@+id/scannerThreshold"
android:layout_width="match_parent"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scannerColors" />
<SeekBar
android:id="@+id/scannerBrightness"
android:layout_width="match_parent"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/scannerThreshold" />
<TextView
android:id="@+id/scannerColorsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="-12dp"
android:text="Posterize"
app:layout_constraintBottom_toTopOf="@+id/scannerColors"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/scannerThresholdLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="-12dp"
android:text="Exposure"
app:layout_constraintBottom_toTopOf="@+id/scannerThreshold"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/scannerBrightnessLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="-12dp"
android:text="Brightness"
app:layout_constraintBottom_toTopOf="@+id/scannerBrightness"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/scannerImage"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:rotation="90"
android:scaleType="fitCenter"
android:scaleX="1"
android:scaleY="1"
android:translationY="-40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".CodeScannerView" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:padding="3dp"
android:background="@drawable/border"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout_editor_absoluteX="3dp">
<TextView
android:id="@+id/scannerColorsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Posterize"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"/>
<SeekBar
android:id="@+id/scannerColors"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="15dp" />
<TextView
android:id="@+id/scannerThresholdLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Exposure"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"/>
<SeekBar
android:id="@+id/scannerThreshold"
android:layout_width="match_parent"
android:layout_height="48dp" />
<TextView
android:id="@+id/scannerBrightnessLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Brightness"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"/>
<SeekBar
android:id="@+id/scannerBrightness"
android:layout_width="match_parent"
android:layout_height="48dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,31 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SeekBar
android:id="@+id/qrSizeSlider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="60dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/qrImage" />
<SeekBar
android:id="@+id/qrSpeedSlider"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="60dp"
app:layout_constraintBottom_toTopOf="@+id/qrSizeSlider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.971"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/qrImage"
app:layout_constraintVertical_bias="0.93" />
<ImageView
android:id="@+id/qrImage"
android:layout_width="match_parent"
@@ -36,23 +15,48 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/qrSpeedText"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="QR Speed"
app:layout_constraintBottom_toTopOf="@+id/qrSpeedSlider"
app:layout_constraintStart_toStartOf="parent" />
android:layout_margin="3dp"
android:orientation="vertical"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/qrSizeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="QR Size"
app:layout_constraintBottom_toTopOf="@+id/qrSizeSlider"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/qrSpeedText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="QR Speed"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintBottom_toTopOf="@+id/qrSpeedSlider" />
<SeekBar
android:id="@+id/qrSpeedSlider"
android:layout_width="match_parent"
android:layout_height="48dp" />
<TextView
android:id="@+id/qrSizeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="QR Size"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
<SeekBar
android:id="@+id/qrSizeSlider"
android:layout_width="match_parent"
android:layout_height="48dp"
tools:layout_editor_absoluteY="135dp" />
</LinearLayout>
<TextView
android:id="@+id/qrIndexN"
@@ -60,7 +64,8 @@
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintEnd_toStartOf="@+id/qrIndexSlash"
app:layout_constraintTop_toBottomOf="@+id/qrImage" />
app:layout_constraintTop_toBottomOf="@+id/qrImage"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"/>
<TextView
android:id="@+id/qrIndexSlash"
@@ -69,7 +74,8 @@
android:text="/"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/qrImage" />
app:layout_constraintTop_toBottomOf="@+id/qrImage"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
<TextView
android:id="@+id/qrIndexD"
@@ -77,6 +83,7 @@
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintStart_toEndOf="@+id/qrIndexSlash"
app:layout_constraintTop_toBottomOf="@+id/qrImage" />
app:layout_constraintTop_toBottomOf="@+id/qrImage"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -8,9 +8,12 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:orientation="horizontal"
android:layout_marginLeft="3dp"
android:layout_marginTop="5dp"
android:layout_marginRight="3dp"
android:layout_marginBottom="3dp"
android:background="@drawable/border"
android:orientation="horizontal"
tools:ignore="UselessParent">
<TextView
@@ -18,8 +21,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:textSize="24sp"
android:overlapAnchor="false"/>
android:overlapAnchor="false"
android:text="▼ Options"
android:textSize="24sp" />
</LinearLayout>
@@ -28,9 +32,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="9dp"
android:layout_marginTop="-5dp"
android:layout_marginTop="-6dp"
android:paddingLeft="3dp"
android:paddingRight="3dp"
android:text="Test"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"/>
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" />
</RelativeLayout>
@@ -11,7 +11,6 @@
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/border"
android:padding="3dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -23,16 +22,18 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<ImageButton
android:id="@+id/match_indicator_back_button"
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="Back"
android:scaleType="fitCenter"
android:src="@drawable/triangle_left"
android:text="Back"
android:background="@color/zxing_transparent"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -42,9 +43,10 @@
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:contentDescription="Next"
android:src="@drawable/triangle_right"
android:text="Next"
android:background="@color/zxing_transparent"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -53,24 +55,24 @@
android:id="@+id/match_indicator_alliance_pos_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginEnd="58dp"
android:text="Temp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/match_indicator_next_button"
app:layout_constraintEnd_toEndOf="@+id/match_indicator_next_button"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/match_indicator_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginStart="58dp"
android:text="Temp"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/match_indicator_back_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pit_indicator_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/border"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<View
android:id="@+id/pit_indicator_background"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<TextView
android:id="@+id/pit_indicator_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="58dp"
android:text="Username"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/pit_indicator_teamnum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="4388"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
@@ -17,6 +17,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="0"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textSize="24sp" />
<Button
+1 -1
View File
@@ -28,7 +28,7 @@
android:id="@+id/team_option_logo"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_margin="3dp"
android:layout_margin="2dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
+1 -1
View File
@@ -1,5 +1,5 @@
[versions]
agp = "8.8.0"
agp = "8.13.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
+1 -1
View File
@@ -1,6 +1,6 @@
#Sun Mar 24 10:48:55 MDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists