26 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
Daniel Carta 5279c085e1 Match Scouting Indicator Overhaul
Redo of the UI for the scouting indicator and overall enhancement of the UI
2025-06-17 11:15:36 -06:00
Michael Mikovsky 41460fcd7e Use android log. Fixes #5 2025-05-27 09:29:57 -06:00
Michael Mikovsky 782fb73050 Delete TODO.md 2025-05-26 11:18:01 -06:00
Michael Mikovsky 7e9954d78a Merge pull request #6 from Team4388/python-file-transfer
Add HTTP file transfer
2025-05-26 17:07:25 +00:00
Michael Mikovsky 65baecac35 Fix crash 2025-05-26 10:13:28 -06:00
Michael Mikovsky e278bc10a1 Add java side of http syncing 2025-05-25 18:48:02 -06:00
Michael Mikovsky 5d727cf359 Add upload and download to python server 2025-05-25 13:51:45 -06:00
Michael Mikovsky ae147771cb Start working on the server 2025-05-25 13:44:37 -06:00
76 changed files with 3412 additions and 1454 deletions
+6
View File
@@ -1,3 +1,9 @@
# Python server
__pycache__/
metadata.json
api_key.txt
server_data/
# Gradle files # Gradle files
.gradle/ .gradle/
build/ build/
+2 -14
View File
@@ -7,9 +7,7 @@
[**Read the wiki**](https://github.com/Team4388/ScoutingApp2025/wiki) [**Read the wiki**](https://github.com/Team4388/ScoutingApp2025/wiki)
[**Test Data**](https://github.com/Team4388/ScoutingApp2025/blob/main/2024week0-1728149849985.scoutbundle) #### Features
#### Here is an overview of the main features currently included in the app:
- This project is written for Android! No need for some kind of janky laptop charging setup. - 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. - 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! - 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 - Deployment on F-Droid
- Data cloud sync using an FTP server - 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 ### Screenshots
|Match scouting interface|Field editor|Teams data viewer| |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)|
-31
View File
@@ -1,31 +0,0 @@
### TODO:
##### Scouting:
- Make an easier way to make game-specific UI elements
##### Data Analysis:
- Add analysis for the rest of the data types besides Tally
- Add a "scout note" system for scouters to contribute to the scouting report?
- Make data that has been marked for rescouting not processed in either by team or by type analysis.
##### Functionality:
- Rewrite FTP transfer to be over HTTP requests
- - Potentially using a python server or some other pre-existing file transfer over HTTP
- Delete file menu
- Make "Sync meta files" option only block uploading fields specifically.
##### UI:
- Update docs and README.md with new features
- Improve file status indicator for scouting
- - The autosave timeout can be significantly reduced, so the save indicator is not a necessity
- - A new system for the rescout indicator should be used.
- Improve UI elements for scouting data by team
- Field button in settings overlaps with other elements in some devices
### In Progress:
##### Scouting:
##### Data Analysis:
##### Functionality:
##### UI:
### Done:
##### Scouting:
##### Data Analysis:
##### Functionality:
##### UI:
+6 -6
View File
@@ -44,8 +44,8 @@ android {
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
} }
aaptOptions { androidResources {
noCompress("tflite"); noCompress += listOf("tflite")
} }
} }
@@ -66,10 +66,10 @@ dependencies {
androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.espresso.core)
var camerax_version = "1.3.2" var camerax_version = "1.4.2"
implementation("androidx.camera:camera-core:1.3.2") implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:1.3.2") implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:1.3.2") implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:${camerax_version}") implementation("androidx.camera:camera-view:${camerax_version}")
implementation("com.journeyapps:zxing-android-embedded:4.3.0") 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[][] { 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 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), 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 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 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 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 auto?", 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 auto?", 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 auto?", 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 auto?", 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 auto?", 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(),"Upper Algae Removal", "Did the robot remove upper Algae?", 0),
new CheckboxType(uuid(),"Lower Algae Removal", "Did the robot remove lower Algae?", 0), new CheckboxType(uuid(),"Lower Algae Removal", "Did the robot remove lower Algae?", 0),
@@ -1,5 +1,7 @@
package com.ridgebotics.ridgescout.scoutingData; package com.ridgebotics.ridgescout.scoutingData;
import android.util.Log;
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType; import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
import com.ridgebotics.ridgescout.types.ScoutingArray; import com.ridgebotics.ridgescout.types.ScoutingArray;
import com.ridgebotics.ridgescout.types.data.RawDataType; import com.ridgebotics.ridgescout.types.data.RawDataType;
@@ -31,15 +33,15 @@ public class ScoutingDataWriter {
switch (data[i].getValueType()){ switch (data[i].getValueType()){
case NUM: case NUM:
bb.addInt((int) data[i].forceGetValue()); bb.addInt((int) data[i].forceGetValue());
System.out.println("Saved INT: " + data[i].getUUID() + ", ("+ data[i].get() +")"); Log.i(ScoutingDataWriter.class.toString(),"Saved INT: " + data[i].getUUID() + ", ("+ data[i].get() +")");
break; break;
case STRING: case STRING:
bb.addString((String) data[i].forceGetValue()); bb.addString((String) data[i].forceGetValue());
System.out.println("Saved STR: " + data[i].getUUID() + ", ("+ data[i].get() +")"); Log.i(ScoutingDataWriter.class.toString(), "Saved STR: " + data[i].getUUID() + ", ("+ data[i].get() +")");
break; break;
case NUMARR: case NUMARR:
bb.addIntArray((int[]) data[i].forceGetValue()); bb.addIntArray((int[]) data[i].forceGetValue());
System.out.println("Saved INT Array: " + data[i].getUUID() + ", ("+ Arrays.toString((int[]) data[i].get()) +")"); Log.i(ScoutingDataWriter.class.toString(), "Saved INT Array: " + data[i].getUUID() + ", ("+ Arrays.toString((int[]) data[i].get()) +")");
} }
} }
byte[] bytes = bb.build(); byte[] bytes = bb.build();
@@ -58,61 +60,57 @@ public class ScoutingDataWriter {
public ScoutingArray data; 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); byte[] bytes = FileEditor.readFile(filename);
BuiltByteParser bbp = new BuiltByteParser(bytes); BuiltByteParser bbp = new BuiltByteParser(bytes);
try { // try {
ArrayList<BuiltByteParser.parsedObject> objects = bbp.parse(); ArrayList<BuiltByteParser.parsedObject> objects = bbp.parse();
RawDataType[] rawDataTypes = new RawDataType[objects.size()-2]; 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.addSimpleError("Error loading " + filename);
AlertManager.error(new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!")); throw new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!");
return null; }
}
// System.out.println(version); // 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++){ for(int i = 0; i < values[version].length; i++){
switch (objects.get(i+2).getType()){ switch (objects.get(i+2).getType()){
case 1: // Int case 1: // Int
rawDataTypes[i] = IntType.newNull(values[version][i].UUID); rawDataTypes[i] = IntType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get()); rawDataTypes[i].forceSetValue(objects.get(i+2).get());
System.out.println("Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break; break;
case 2: // String case 2: // String
rawDataTypes[i] = StringType.newNull(values[version][i].UUID); rawDataTypes[i] = StringType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get()); rawDataTypes[i].forceSetValue(objects.get(i+2).get());
System.out.println("Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")");
break; break;
case 3: // Int array case 3: // Int array
rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID); rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID);
rawDataTypes[i].forceSetValue(objects.get(i+2).get()); rawDataTypes[i].forceSetValue(objects.get(i+2).get());
System.out.println("Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")"); Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")");
break; 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 // 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();
}
}
@@ -1,5 +1,7 @@
package com.ridgebotics.ridgescout.types; package com.ridgebotics.ridgescout.types;
import android.util.Log;
import com.ridgebotics.ridgescout.scoutingData.transfer.CreateTransferType; import com.ridgebotics.ridgescout.scoutingData.transfer.CreateTransferType;
import com.ridgebotics.ridgescout.scoutingData.transfer.DirectTransferType; import com.ridgebotics.ridgescout.scoutingData.transfer.DirectTransferType;
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType; import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
@@ -44,7 +46,7 @@ public class ScoutingArray {
} }
this.array = new_values; this.array = new_values;
version++; version++;
System.out.println("Updated to " + version); Log.i(getClass().toString(),"Updated to " + version);
} }
} }
@@ -9,14 +9,13 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.IntStream; import java.util.stream.IntStream;
// Class to contain data for an entire event. // Class to contain data for an entire event.
// Easily encoded and decoded to binary format. // Easily encoded and decoded to binary format.
public class frcEvent { public class frcEvent {
public static final int typecode = 254;
public String eventCode; public String eventCode;
public String name; public String name;
public ArrayList<frcMatch> matches; public ArrayList<frcMatch> matches;
@@ -134,29 +133,29 @@ public class frcEvent {
return null; return null;
} }
// public
// Returns the soonest match that there will be all the possible upcoming data on other teams // Returns the soonest match that there will be all the possible upcoming data on other teams
public void getReportMatches(int ourTeamNum){ public int getMostInformedBy(int ourTeamNum, int curMatch){
frcMatch[] teamMatches = event.getTeamMatches(ourTeamNum); frcMatch teamMatch = getNextTeamMatch(ourTeamNum, curMatch);
for(int i = 0; i < teamMatches.length; i++){ int maxMatch = Integer.MIN_VALUE;
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];
if(teamNum == ourTeamNum) for(int a = 0; a < 6; a++){
continue; int teamNum;
if(a < 3)
teamNum = teamMatch.redAlliance[a];
else
teamNum = teamMatch.blueAlliance[a-3];
int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex); if(teamNum == ourTeamNum)
if(maxMatch < matchNum) continue;
maxMatch = matchNum;
} int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatch.matchIndex);
if(maxMatch < matchNum)
maxMatch = matchNum;
} }
return maxMatch;
} }
public frcTeam getTeamByNum(int teamNum){ public frcTeam getTeamByNum(int teamNum){
@@ -168,6 +167,26 @@ public class frcEvent {
return null; 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){ public boolean getIsBlueAlliance(int teamNum, int matchNum){
return getIsBlueAlliance(teamNum, matches.get(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.PieData;
import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry; import com.github.mikephil.charting.data.PieEntry;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -105,16 +106,14 @@ public class DropdownType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){ public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return; if(data.isNull()) return;
TextView tv = new TextView(parent.getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams( parent.addView(
ViewGroup.LayoutParams.MATCH_PARENT, new TextViewBuilder(parent.getContext(), text_options[(int) data.get()])
ViewGroup.LayoutParams.WRAP_CONTENT .layout_match_wrap()
)); .padding(20)
tv.setPadding(20,20,20,20); .size(18)
tv.setGravity(Gravity.CENTER_HORIZONTAL); .align_center()
tv.setText(text_options[(int) data.get()]); .build());
tv.setTextSize(18);
parent.addView(tv);
} }
@@ -18,6 +18,7 @@ import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; 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.RawDataType;
import com.ridgebotics.ridgescout.types.data.IntArrType; import com.ridgebotics.ridgescout.types.data.IntArrType;
import com.ridgebotics.ridgescout.ui.views.FieldPosView; import com.ridgebotics.ridgescout.ui.views.FieldPosView;
@@ -31,14 +32,58 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
public class FieldposType extends FieldType { 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 int get_byte_id() {return fieldposType;}
public inputTypes getInputType(){return inputTypes.FIELDPOS;} public inputTypes getInputType(){return inputTypes.FIELDPOS;}
public RawDataType.valueTypes getValueType(){return RawDataType.valueTypes.NUM;} public RawDataType.valueTypes getValueType(){return RawDataType.valueTypes.NUM;}
public Object get_fallback_value(){return 0;} public Object get_fallback_value(){return 0;}
public FieldposType(){} public FieldposType(){}
public String get_type_name(){return "Field Pos";} 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); super(UUID, name, description);
this.fieldImage = fieldImage;
this.default_value = default_value; this.default_value = default_value;
} }
@@ -47,11 +92,14 @@ public class FieldposType extends FieldType {
public void encodeData(ByteBuilder bb) throws ByteBuilder.buildingException { public void encodeData(ByteBuilder bb) throws ByteBuilder.buildingException {
bb.addInt(this.fieldImage.get_index());
bb.addIntArray((int[]) default_value); bb.addIntArray((int[]) default_value);
} }
public void decodeData(ArrayList<BuiltByteParser.parsedObject> objects) { 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)); onUpdate.apply(new IntArrType(name, pos));
}); });
setViewValue(default_value); setViewValue(default_value);
field.setFieldImage(fieldImage);
return field; return field;
} }
@@ -102,6 +151,7 @@ public class FieldposType extends FieldType {
FieldPosView fp = new FieldPosView(parent.getContext()); FieldPosView fp = new FieldPosView(parent.getContext());
fp.setEnabled(false); fp.setEnabled(false);
fp.setPos((int[]) data.get()); fp.setPos((int[]) data.get());
fp.setFieldImage(this.fieldImage);
parent.addView(fp); 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){ public void add_compiled_view(LinearLayout parent, RawDataType[] data){
MultiFieldPosView mfp = new MultiFieldPosView(parent.getContext()); MultiFieldPosView mfp = new MultiFieldPosView(parent.getContext());
for(int i = 0; i < data.length; i++){ for(int i = 0; i < data.length; i++){
if(data[i].isNull()) continue; if(data[i].isNull()) continue;
mfp.addPos((int[]) data[i].get()); mfp.addPos((int[]) data[i].get());
} }
mfp.setFieldImage(fieldImage);
parent.addView(mfp); 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.types.data.IntType;
import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.ByteBuilder; import com.ridgebotics.ridgescout.utility.ByteBuilder;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -118,16 +119,11 @@ public class NumberType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){ public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return; if(data.isNull()) return;
parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get()))
TextView tv = new TextView(parent.getContext()); .layout_match_wrap()
tv.setLayoutParams(new FrameLayout.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(24)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(String.valueOf((int) data.get()));
tv.setTextSize(24);
parent.addView(tv);
} }
@@ -29,6 +29,7 @@ import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.data.LineDataSet;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -103,16 +104,11 @@ public class TallyType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){ public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return; if(data.isNull()) return;
parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get()))
TextView tv = new TextView(parent.getContext()); .layout_match_wrap()
tv.setLayoutParams(new FrameLayout.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(24)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(String.valueOf((int) data.get()));
tv.setTextSize(24);
parent.addView(tv);
} }
@@ -344,16 +340,12 @@ public class TallyType extends FieldType {
row = new TableRow(parent.getContext()); row = new TableRow(parent.getContext());
CandlestickView view = views.get(i); CandlestickView view = views.get(i);
TextView teamNum = new TextView(parent.getContext()); row.addView(new TextViewBuilder(parent.getContext(), String.valueOf(view.teamNum))
TableRow.LayoutParams params = new TableRow.LayoutParams(); .align_center()
params.gravity = Gravity.CENTER; .padding(10)
teamNum.setLayoutParams(params); .h6()
teamNum.setPadding(10,10,10,10); .build());
teamNum.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6);
teamNum.setText(String.valueOf(view.teamNum));
row.addView(teamNum);
row.addView(view); row.addView(view);
parent.addView(row); 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.Entry;
import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.data.LineDataSet;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -112,15 +113,11 @@ public class TextType extends FieldType {
public void add_individual_view(LinearLayout parent, RawDataType data){ public void add_individual_view(LinearLayout parent, RawDataType data){
if(data.isNull()) return; if(data.isNull()) return;
TextView tv = new TextView(parent.getContext()); parent.addView(new TextViewBuilder(parent.getContext(), (String) data.get())
tv.setLayoutParams(new FrameLayout.LayoutParams( .layout_match_wrap()
ViewGroup.LayoutParams.MATCH_PARENT, .align_center()
ViewGroup.LayoutParams.WRAP_CONTENT .size(18)
)); .build());
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText((String) data.get());
tv.setTextSize(18);
parent.addView(tv);
} }
@@ -144,25 +141,20 @@ public class TextType extends FieldType {
positive_mean = 0; positive_mean = 0;
count = 0; count = 0;
positive_text = new TextView(parent.getContext()); positive_text = new TextViewBuilder(parent.getContext())
positive_text.setLayoutParams(new FrameLayout.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(20)
ViewGroup.LayoutParams.WRAP_CONTENT .build();
));
positive_text.setGravity(Gravity.CENTER_HORIZONTAL);
positive_text.setTextSize(20);
parent.addView(positive_text); parent.addView(positive_text);
for (int i = 0; i < data.length; i++){ for (int i = 0; i < data.length; i++){
if (!data[i].isNull()) { if (!data[i].isNull()) {
SentimentAnalysis.analyse((String) data[i].get(), new SentimentAnalysis.resultCallback() { SentimentAnalysis.analyse((String) data[i].get(), sentiment -> {
@Override positive_mean += sentiment;
public void onFinish(float sentiment) { count++;
positive_mean += sentiment;
count++;
positive_text.setText("Sentiment: " + (positive_mean / count)); positive_text.setText("Sentiment: " + (positive_mean / count));
}
}); });
} }
} }
@@ -10,6 +10,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.rescout_list;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -57,13 +58,13 @@ public class FieldDataFragment extends Fragment {
for (int teamIndex = 0; teamIndex < event.teams.size(); teamIndex++) { for (int teamIndex = 0; teamIndex < event.teams.size(); teamIndex++) {
int teamNum = event.teams.get(teamIndex).teamNumber; int teamNum = event.teams.get(teamIndex).teamNumber;
List<String> filenames = new ArrayList<>(List.of(FileEditor.getMatchesByTeamNum(evcode, 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<>(); ArrayList<RawDataType> teamData = new ArrayList<>();
for (int i = 0; i < filenames.size(); i++) { for (int i = 0; i < filenames.size(); i++) {
try { try {
System.out.println("Loading: " + filenames.get(i)); Log.i(getClass().toString(), "Loading: " + filenames.get(i));
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filenames.get(i), match_values, match_transferValues); ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filenames.get(i), match_values, match_transferValues);
if (psda.data.array[fieldIndex] != null && psda.data.array[fieldIndex].get() != null && !psda.data.array[fieldIndex].isNull()) if (psda.data.array[fieldIndex] != null && psda.data.array[fieldIndex].get() != null && !psda.data.array[fieldIndex].isNull())
teamData.add(psda.data.array[fieldIndex]); teamData.add(psda.data.array[fieldIndex]);
@@ -75,7 +76,7 @@ public class FieldDataFragment extends Fragment {
data.put(teamNum, teamData); data.put(teamNum, teamData);
} }
System.out.println("Finished!"); Log.i(getClass().toString(), "Finished!");
@@ -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_transferValues;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_values; import static com.ridgebotics.ridgescout.utility.DataManager.pit_values;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -23,6 +25,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentDataTeamsBinding; import com.ridgebotics.ridgescout.databinding.FragmentDataTeamsBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; 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.types.frcTeam;
import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -66,38 +70,37 @@ public class TeamsFragment extends Fragment {
loadTeam(index); 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()); loadTeam(SettingsManager.getTeamsDataMode());
return binding.getRoot(); 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) { 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); 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_pit_data(team);}catch(Exception e){AlertManager.error(e);}
try {add_match_data(team, mode);}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(); binding.pitArea.removeAllViews();
final String filename = evcode+"-"+team.teamNumber+".pitscoutdata"; final String filename = evcode+"-"+team.teamNumber+".pitscoutdata";
@@ -117,49 +120,34 @@ public class TeamsFragment extends Fragment {
// ll.addView(new MaterialDivider(getContext())); // ll.addView(new MaterialDivider(getContext()));
if(!FileEditor.fileExist(filename)){ if(!FileEditor.fileExist(filename)){
TextView tv = new TextView(getContext()); binding.pitArea.addView(new TextViewBuilder(getContext(), "No pit data has been collected!")
tv.setLayoutParams(new FrameLayout.LayoutParams( .layout_match_wrap().align_center().size(23).build());
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);
return; return;
} }
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filename, pit_values, pit_transferValues); ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filename, pit_values, pit_transferValues);
TextView tv = new TextView(getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams( binding.pitArea.addView(new TextViewBuilder(getContext(), "Pit scouting by " + psda.username)
ViewGroup.LayoutParams.MATCH_PARENT, .layout_match_wrap()
ViewGroup.LayoutParams.WRAP_CONTENT .padding(0, 20, 0, 5)
)); .align_center()
tv.setPadding(0, 20, 0, 5); .size(30)
tv.setGravity(Gravity.CENTER_HORIZONTAL); .build()
tv.setText("Pit scouting by " + psda.username); );
tv.setTextSize(30);
binding.pitArea.addView(tv);
for (int a = 0; a < psda.data.array.length; a++) { for (int a = 0; a < psda.data.array.length; a++) {
tv = new TextView(getContext()); TextViewBuilder tvb = new TextViewBuilder(getContext(), pit_latest_values[a].name)
tv.setLayoutParams(new FrameLayout.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .layout_match_wrap()
ViewGroup.LayoutParams.WRAP_CONTENT .size(25);
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(pit_latest_values[a].name);
tv.setTextSize(25);
if(psda.data.array[a].isNull()){ if(psda.data.array[a].isNull()){
tv.setBackgroundColor(toggletitle_unselected); tvb.tv.setBackgroundColor(toggletitle_unselected);
tv.setTextColor(toggletitle_black_background); tvb.tv.setTextColor(toggletitle_black_background);
} }
binding.pitArea.addView(tvb.build());
binding.pitArea.addView(tv);
pit_latest_values[a].add_individual_view(binding.pitArea, psda.data.array[a]); 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); String[] files = FileEditor.getMatchesByTeamNum(evcode, team.teamNumber);
if(files.length == 0){ if(files.length == 0){
TextView tv = new TextView(getContext()); binding.matchArea.addView(new TextViewBuilder(getContext(), "No match data has been collected!")
tv.setLayoutParams(new FrameLayout.LayoutParams( .layout_match_wrap()
ViewGroup.LayoutParams.MATCH_PARENT, .align_center()
ViewGroup.LayoutParams.WRAP_CONTENT .size(23)
)); .build());
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("No match data has been collected!");
tv.setTextSize(23);
binding.matchArea.addView(tv);
return; return;
} }
@@ -236,33 +220,28 @@ public class TeamsFragment extends Fragment {
ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[matchIndex], match_values, match_transferValues); ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[matchIndex], match_values, match_transferValues);
TextView tv = new TextView(getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams( binding.matchArea.addView(
ViewGroup.LayoutParams.MATCH_PARENT, new TextViewBuilder(getContext(), "M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username)
ViewGroup.LayoutParams.WRAP_CONTENT .align_center()
)); .size(30)
tv.setPadding(0, 40, 0, 5); .padding(0,0,40,5)
tv.setGravity(Gravity.CENTER_HORIZONTAL); .build()
tv.setText("M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username);
tv.setTextSize(30); );
binding.matchArea.addView(tv);
for (int i = 0; i < psda.data.array.length; i++) { for (int i = 0; i < psda.data.array.length; i++) {
tv = new TextView(getContext()); TextViewBuilder tv = new TextViewBuilder(getContext(), match_latest_values[i].name)
tv.setLayoutParams(new FrameLayout.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(25);
ViewGroup.LayoutParams.WRAP_CONTENT
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(match_latest_values[i].name);
tv.setTextSize(25);
if (psda.data.array[i].isNull()) { if (psda.data.array[i].isNull()) {
tv.setBackgroundColor(toggletitle_unselected); tv.tv.setBackgroundColor(toggletitle_unselected);
tv.setTextColor(toggletitle_black_background); tv.tv.setTextColor(toggletitle_black_background);
} }
binding.matchArea.addView(tv); binding.matchArea.addView(tv.build());
if(psda.data.array[i] != null) 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++){ for(int i = 0; i < match_latest_values.length; i++){
TextView tv = new TextView(getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams( binding.matchArea.addView(
ViewGroup.LayoutParams.MATCH_PARENT, new TextViewBuilder(getContext(), match_latest_values[i].name)
ViewGroup.LayoutParams.WRAP_CONTENT .align_center()
)); .padding(0, 0, 20, 5)
tv.setPadding(0, 20, 0, 5); .size(30)
tv.setGravity(Gravity.CENTER_HORIZONTAL); .build()
tv.setText(match_latest_values[i].name); );
tv.setTextSize(30);
binding.matchArea.addView(tv);
if(data[i] != null) if(data[i] != null)
match_latest_values[i].add_compiled_view(binding.matchArea, data[i]); 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++){ for(int i = 0; i < match_latest_values.length; i++){
TextView tv = new TextView(getContext());
tv.setLayoutParams(new FrameLayout.LayoutParams( binding.matchArea.addView(
ViewGroup.LayoutParams.MATCH_PARENT, new TextViewBuilder(getContext(), match_latest_values[i].name)
ViewGroup.LayoutParams.WRAP_CONTENT .align_center()
)); .size(30)
tv.setPadding(0, 20, 0, 5); .padding(0,0,20,5)
tv.setGravity(Gravity.CENTER_HORIZONTAL); .build()
tv.setText(match_latest_values[i].name); );
tv.setTextSize(30);
binding.matchArea.addView(tv);
if(data[i] != null) if(data[i] != null)
match_latest_values[i].add_history_view(binding.matchArea, data[i]); match_latest_values[i].add_history_view(binding.matchArea, data[i]);
} }
} }
} }
@@ -8,6 +8,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.event;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import android.text.InputType; import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -29,6 +30,7 @@ import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.types.frcMatch; import com.ridgebotics.ridgescout.types.frcMatch;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -58,11 +60,10 @@ public class EventFragment extends Fragment {
add_match_scouting(event); add_match_scouting(event);
} }
private void addTableText(TableRow tr, String textStr){ private void addTableText(TableRow tr, String textStr){
TextView text = new TextView(getContext()); tr.addView(new TextViewBuilder(getContext(), textStr)
text.setTextSize(18); .align_center()
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center .size(18)
text.setText(textStr); .build());
tr.addView(text);
} }
public void add_pit_scouting(frcEvent event){ public void add_pit_scouting(frcEvent event){
@@ -93,33 +94,32 @@ public class EventFragment extends Fragment {
tr = new TableRow(getContext()); tr = new TableRow(getContext());
} }
TextView text = new TextView(getContext()); TextViewBuilder text = new TextViewBuilder(getContext(), String.valueOf(num))
text.setTextSize(18); .size(18)
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); .align_center();
text.setText(String.valueOf(num));
final String filename = event.eventCode + "-" + num + ".pitscoutdata"; final String filename = event.eventCode + "-" + num + ".pitscoutdata";
if(FileEditor.fileExist(filename)){ if(FileEditor.fileExist(filename)){
final boolean[] rescout = {DataManager.rescout_list.contains(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]; rescout[0] = !rescout[0];
if(rescout[0]) { if(rescout[0]) {
text.setBackgroundColor(color_rescout); text.tv.setBackgroundColor(color_rescout);
DataManager.rescout_list.add(filename); DataManager.rescout_list.add(filename);
}else{ }else{
text.setBackgroundColor(color_found); text.tv.setBackgroundColor(color_found);
DataManager.rescout_list.remove(filename); DataManager.rescout_list.remove(filename);
} }
DataManager.save_rescout_list(); DataManager.save_rescout_list();
return true; return true;
}); });
}else{ }else{
text.setBackgroundColor(color_not_found); text.tv.setBackgroundColor(color_not_found);
} }
tr.addView(text); tr.addView(text.build());
} }
if(tr != null) if(tr != null)
binding.teamsTable.addView(tr); binding.teamsTable.addView(tr);
@@ -152,10 +152,6 @@ public class EventFragment extends Fragment {
addTableText(tr, String.valueOf(match.matchIndex)); addTableText(tr, String.valueOf(match.matchIndex));
// //
for(int i=0;i<6;i++){ for(int i=0;i<6;i++){
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
int team_num; int team_num;
String alliance_position; String alliance_position;
@@ -167,20 +163,23 @@ public class EventFragment extends Fragment {
alliance_position = "blue-"+(i-2); 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"; final String filename = event.eventCode + "-" + match.matchIndex + "-" + alliance_position + "-" + team_num + ".matchscoutdata";
if(FileEditor.fileExist(filename)){ if(FileEditor.fileExist(filename)){
final boolean[] rescout = {DataManager.rescout_list.contains(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]; rescout[0] = !rescout[0];
if(rescout[0]) { if(rescout[0]) {
text.setBackgroundColor(color_rescout); text.tv.setBackgroundColor(color_rescout);
DataManager.rescout_list.add(filename); DataManager.rescout_list.add(filename);
}else{ }else{
text.setBackgroundColor(color_found); text.tv.setBackgroundColor(color_found);
DataManager.rescout_list.remove(filename); DataManager.rescout_list.remove(filename);
} }
DataManager.save_rescout_list(); DataManager.save_rescout_list();
@@ -188,9 +187,9 @@ public class EventFragment extends Fragment {
}); });
}else{ }else{
text.setBackgroundColor(color_not_found); text.tv.setBackgroundColor(color_not_found);
} }
tr.addView(text); tr.addView(text.build());
} }
binding.matchTable.addView(tr); binding.matchTable.addView(tr);
@@ -276,7 +275,7 @@ public class EventFragment extends Fragment {
builder.setPositiveButton("OK", (dialogInterface, i) -> { builder.setPositiveButton("OK", (dialogInterface, i) -> {
int index = dropdown.getIndex(); int index = dropdown.getIndex();
System.out.println(index); Log.i(getClass().toString(), String.valueOf(index));
if(!(index >= 0 && index < teamNums.size())) return; if(!(index >= 0 && index < teamNums.size())) return;
event.teams.remove(index); event.teams.remove(index);
@@ -9,6 +9,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.event;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -20,6 +21,7 @@ import androidx.fragment.app.Fragment;
import com.google.android.material.divider.MaterialDivider; import com.google.android.material.divider.MaterialDivider;
import com.ridgebotics.ridgescout.ui.views.ToggleTitleView; import com.ridgebotics.ridgescout.ui.views.ToggleTitleView;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingMatchBinding; import com.ridgebotics.ridgescout.databinding.FragmentScoutingMatchBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
@@ -31,6 +33,7 @@ import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.AutoSaveManager; import com.ridgebotics.ridgescout.utility.AutoSaveManager;
import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
// Fragment for match scouting data editing. // Fragment for match scouting data editing.
public class MatchScoutingFragment extends Fragment { public class MatchScoutingFragment extends Fragment {
@@ -48,24 +51,27 @@ public class MatchScoutingFragment extends Fragment {
alliance_position = SettingsManager.getAllyPos(); alliance_position = SettingsManager.getAllyPos();
username = SettingsManager.getUsername(); username = SettingsManager.getUsername();
binding.username.setText(username); binding.bindicator.setUsername(username);
binding.alliancePosText.setText(alliance_position); binding.bindicator.setAlliancePos(alliance_position);
binding.bindicator.bringToFront();
binding.matchTeamCard.setVisibility(View.GONE); binding.matchTeamCard.setVisibility(View.GONE);
clear_fields(); clear_fields();
binding.matchTeamCard.setVisibility(View.VISIBLE); binding.matchTeamCard.setVisibility(View.VISIBLE);
if(DataManager.match_values == null || DataManager.match_values.length == 0){ 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."); binding.MatchScoutArea.addView(
tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); new TextViewBuilder(getContext(), "Failed to load fields.\nTry to either download or create match scouting fields.")
binding.MatchScoutArea.addView(tv); .align_center()
.build());
return binding.getRoot(); return binding.getRoot();
} }
binding.nextButton.setOnClickListener(v -> { binding.bindicator.match_indicator_next_button.setOnClickListener(v -> {
if(edited) save(); if(edited) save();
SettingsManager.setMatchNum(cur_match_num+1); SettingsManager.setMatchNum(cur_match_num+1);
cur_match_num += 1; cur_match_num += 1;
@@ -74,21 +80,21 @@ public class MatchScoutingFragment extends Fragment {
}); });
if(SettingsManager.getEnableQuickAlliancePosChange()) if(SettingsManager.getEnableQuickAlliancePosChange())
binding.fileIndicator.setOnClickListener(v -> { binding.bindicator.setOnClickListener(v -> {
// if(e.getAction() != MotionEvent.ACTION_MOVE) return true; // if(e.getAction() != MotionEvent.ACTION_MOVE) return true;
// System.out.println(e.getAxisValue(0)); // System.out.println(e.getAxisValue(0));
if(edited) save(); if(edited) save();
alliance_position = incrementMatchPos(alliance_position); alliance_position = incrementMatchPos(alliance_position);
SettingsManager.setAllyPos(alliance_position); SettingsManager.setAllyPos(alliance_position);
binding.alliancePosText.setText(alliance_position); binding.bindicator.setAlliancePos(alliance_position);
update_match_num(); update_match_num();
update_scouting_data(); update_scouting_data();
// return true; // return true;
}); });
binding.backButton.setOnClickListener(v -> { binding.bindicator.match_indicator_back_button.setOnClickListener(v -> {
if(edited) save(); if(edited) save();
SettingsManager.setMatchNum(cur_match_num-1); SettingsManager.setMatchNum(cur_match_num-1);
cur_match_num -= 1; cur_match_num -= 1;
@@ -150,7 +156,7 @@ public class MatchScoutingFragment extends Fragment {
public void save(){ public void save(){
System.out.println("Saved!"); Log.i(this.getClass().toString(), "Saved!");
edited = false; edited = false;
enableRescoutButton(); enableRescoutButton();
AlertManager.toast("Saved " + filename); AlertManager.toast("Saved " + filename);
@@ -158,7 +164,7 @@ public class MatchScoutingFragment extends Fragment {
} }
public void set_indicator_color(int color){ public void set_indicator_color(int color){
binding.fileIndicator.setBackgroundColor(color); binding.bindicator.setColor(color);
} }
public void update_asm(){ public void update_asm(){
@@ -238,18 +244,18 @@ public class MatchScoutingFragment extends Fragment {
edited = false; edited = false;
binding.matchnum.setText(String.valueOf(cur_match_num+1)); binding.bindicator.setMatchNum(String.valueOf(cur_match_num+1));
if(cur_match_num <= 0){ if(cur_match_num <= 0){
binding.backButton.setVisibility(View.GONE); binding.bindicator.match_indicator_back_button.setVisibility(View.GONE);
}else{ }else{
binding.backButton.setVisibility(View.VISIBLE); binding.bindicator.match_indicator_back_button.setVisibility(View.VISIBLE);
} }
if(cur_match_num >= event.matches.size()-1){ if(cur_match_num >= event.matches.size()-1){
binding.nextButton.setVisibility(View.GONE); binding.bindicator.match_indicator_next_button.setVisibility(View.GONE);
}else{ }else{
binding.nextButton.setVisibility(View.VISIBLE); binding.bindicator.match_indicator_next_button.setVisibility(View.VISIBLE);
} }
} }
@@ -271,7 +277,7 @@ public class MatchScoutingFragment extends Fragment {
break; break;
} }
binding.barTeamNum.setText(String.valueOf(team_num)); binding.bindicator.setTeamNum(String.valueOf(team_num));
frcTeam team = null; frcTeam team = null;
for(int i=0; i < event.teams.size(); i++){ for(int i=0; i < event.teams.size(); i++){
@@ -345,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); ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.match_values, DataManager.match_transferValues);
RawDataType[] types = psdr.data.array; RawDataType[] types = psdr.data.array;
@@ -377,14 +383,14 @@ public class MatchScoutingFragment extends Fragment {
} }
if(ScoutingDataWriter.save(DataManager.match_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types)) if(ScoutingDataWriter.save(DataManager.match_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types))
System.out.println("Saved!"); Log.i(getClass().toString(), "Saved!");
else else
System.out.println("Error saving"); Log.i(getClass().toString(), "Error saving");
} }
private void enableRescoutButton(){ private void enableRescoutButton(){
set_indicator_color(rescout ? rescout_color : saved_color); set_indicator_color(rescout ? rescout_color : saved_color);
binding.fileIndicator.setOnLongClickListener(v -> { binding.bindicator.setOnLongClickListener(v -> {
rescout = !rescout; rescout = !rescout;
if(rescout){ if(rescout){
set_indicator_color(rescout_color); set_indicator_color(rescout_color);
@@ -401,6 +407,6 @@ public class MatchScoutingFragment extends Fragment {
} }
private void disableRescoutButton(){ private void disableRescoutButton(){
binding.fileIndicator.setOnLongClickListener(null); binding.bindicator.setOnLongClickListener(null);
} }
} }
@@ -10,6 +10,7 @@ import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues;
import static com.ridgebotics.ridgescout.utility.DataManager.pit_values; import static com.ridgebotics.ridgescout.utility.DataManager.pit_values;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -19,8 +20,10 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.divider.MaterialDivider; 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.ui.views.ToggleTitleView;
import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingPitBinding; import com.ridgebotics.ridgescout.databinding.FragmentScoutingPitBinding;
import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter;
@@ -75,7 +78,6 @@ public class PitScoutingFragment extends Fragment {
String fileUsernames = ""; String fileUsernames = "";
ToggleTitleView[] titles; ToggleTitleView[] titles;
AutoSaveManager asm = new AutoSaveManager(this::save, AUTO_SAVE_DELAY); AutoSaveManager asm = new AutoSaveManager(this::save, AUTO_SAVE_DELAY);
ArrayList<RawDataType> rawDataTypes; ArrayList<RawDataType> rawDataTypes;
@@ -91,14 +93,14 @@ public class PitScoutingFragment extends Fragment {
} }
if(ScoutingDataWriter.save(pit_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types)) { if(ScoutingDataWriter.save(pit_values.length-1, ScoutingDataWriter.checkAddName(fileUsernames, username), filename, types)) {
System.out.println("Saved!"); Log.i(getClass().toString(), "Saved!");
AlertManager.toast("Saved " + filename); Log.i(getClass().toString(), "Saved " + filename);
}else }else
System.out.println("Error saving"); Log.i(getClass().toString(), "Error saving");
} }
public void set_indicator_color(int color){ public void set_indicator_color(int color){
binding.pitFileIndicator.setBackgroundColor(color); binding.pitIndicator.setColor(color);
} }
public void update_asm(){ public void update_asm(){
@@ -113,10 +115,9 @@ public class PitScoutingFragment extends Fragment {
public void loadTeam(){ public void loadTeam(){
// clear_fields(); // clear_fields();
binding.pitFileIndicator.setVisibility(View.VISIBLE);
binding.pitsTeamCard.setVisibility(View.VISIBLE); binding.pitsTeamCard.setVisibility(View.VISIBLE);
binding.pitBarTeamNum.setText(String.valueOf(team.teamNumber)); binding.pitIndicator.setTeamNum(team.teamNumber);
binding.pitUsername.setText(SettingsManager.getUsername()); binding.pitIndicator.setUsername(SettingsManager.getUsername());
binding.pitsTeamCard.fromTeam(team); binding.pitsTeamCard.fromTeam(team);
filename = evcode + "-" + team.teamNumber + ".pitscoutdata"; filename = evcode + "-" + team.teamNumber + ".pitscoutdata";
@@ -145,7 +146,7 @@ public class PitScoutingFragment extends Fragment {
} }
} }
binding.pitFileIndicator.bringToFront(); binding.pitIndicator.bringToFront();
asm.start(); asm.start();
@@ -153,7 +154,7 @@ public class PitScoutingFragment extends Fragment {
private void enableRescoutButton(){ private void enableRescoutButton(){
set_indicator_color(rescout ? rescout_color : saved_color); set_indicator_color(rescout ? rescout_color : saved_color);
binding.pitFileIndicator.setOnLongClickListener(v -> { binding.pitIndicator.setOnLongClickListener(v -> {
rescout = !rescout; rescout = !rescout;
if(rescout){ if(rescout){
set_indicator_color(rescout_color); set_indicator_color(rescout_color);
@@ -170,7 +171,7 @@ public class PitScoutingFragment extends Fragment {
} }
private void disableRescoutButton(){ private void disableRescoutButton(){
binding.pitFileIndicator.setOnLongClickListener(null); binding.pitIndicator.setOnLongClickListener(null);
} }
@@ -224,7 +225,7 @@ public class PitScoutingFragment extends Fragment {
} }
} }
public void get_fields(){ public void get_fields() throws BuiltByteParser.byteParsingExeption{
ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, pit_values, pit_transferValues); ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, pit_values, pit_transferValues);
RawDataType[] types = psdr.data.array; RawDataType[] types = psdr.data.array;
@@ -15,6 +15,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -22,10 +23,12 @@ import androidx.fragment.app.Fragment;
import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.databinding.FragmentScoutingBinding; import com.ridgebotics.ridgescout.databinding.FragmentScoutingBinding;
import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
@@ -88,7 +91,7 @@ public class ScoutingFragment extends Fragment {
binding.textMatchAlliance.setVisibility(View.GONE); binding.textMatchAlliance.setVisibility(View.GONE);
binding.textName.setVisibility(View.GONE); binding.textName.setVisibility(View.GONE);
binding.textNextMatch.setVisibility(View.GONE); binding.infoBox.setVisibility(View.GONE);
binding.textRescoutIndicator.setVisibility(View.GONE); binding.textRescoutIndicator.setVisibility(View.GONE);
binding.matchScoutingButton.setEnabled(false); binding.matchScoutingButton.setEnabled(false);
@@ -122,15 +125,7 @@ public class ScoutingFragment extends Fragment {
findNavController(this).navigate(R.id.action_navigation_scouting_to_navigation_scouting_event); findNavController(this).navigate(R.id.action_navigation_scouting_to_navigation_scouting_event);
}); });
updateDashboard();
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
int matchNum = SettingsManager.getMatchNum();
int nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex;
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());
return binding.getRoot(); return binding.getRoot();
} }
@@ -159,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.SliderType;
import com.ridgebotics.ridgescout.types.input.TallyType; import com.ridgebotics.ridgescout.types.input.TallyType;
import com.ridgebotics.ridgescout.types.input.TextType; 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.AlertManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID; import java.util.UUID;
// Class to help with fields editor fragment, containing the defaults for each field. // Class to help with fields editor fragment, containing the defaults for each field.
@@ -29,7 +34,8 @@ public class FieldEditorHelper {
paramNumber, paramNumber,
paramString, paramString,
paramStringArray, paramStringArray,
paramNumberArray paramNumberArray,
paramDropdown
} }
public static class parameterType { 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 static class paramNumberArray extends parameterType{
// public int[] val; // public int[] val;
// public paramNumberArray(String name, int[] val){ // public paramNumberArray(String name, int[] val){
@@ -166,9 +183,16 @@ public class FieldEditorHelper {
} }
private static parameterType[] getFieldPosParam(FieldposType s){ 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[]{ return new parameterType[]{
new paramString("Name", s.name), new paramString("Name", s.name),
new paramString("Description", s.description), new paramString("Description", s.description),
new paramDropdown("Field Image", images, 0),
new paramNumber("Default X", ((int[]) s.default_value)[0]), new paramNumber("Default X", ((int[]) s.default_value)[0]),
new paramNumber("Default Y", ((int[]) s.default_value)[1]) new paramNumber("Default Y", ((int[]) s.default_value)[1])
}; };
@@ -218,9 +242,10 @@ public class FieldEditorHelper {
public static void setFieldPosParam(FieldposType s, parameterType[] types){ public static void setFieldPosParam(FieldposType s, parameterType[] types){
s.name = ((paramString) types[0]).val; s.name = ((paramString) types[0]).val;
s.description = ((paramString) types[1]).val; s.description = ((paramString) types[1]).val;
s.fieldImage = FieldposType.FieldImage.from_index(((paramDropdown) types[2]).val);
s.default_value = new int[]{ 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; 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){ private static View createEdit(Context c, parameterType t){
switch (t.id){ switch (t.id){
case paramNumber: case paramNumber:
@@ -314,6 +357,8 @@ public class FieldEditorHelper {
return createStringEdit(c, ((paramString) t).val); return createStringEdit(c, ((paramString) t).val);
case paramStringArray: case paramStringArray:
return createStringArrayEdit(c, ((paramStringArray) t).val); return createStringArrayEdit(c, ((paramStringArray) t).val);
case paramDropdown:
return createDropdown(c, t.name, ((paramDropdown) t).options, ((paramDropdown) t).val);
} }
return null; return null;
} }
@@ -321,22 +366,27 @@ public class FieldEditorHelper {
private static boolean readEdit(View v, parameterType t){ private static boolean readEdit(View v, parameterType t){
try{ try{
String val; // String val;
switch (t.id) { switch (t.id) {
case paramNumber: case paramNumber:
val = ((EditText) v).getText().toString(); String val1 = ((EditText) v).getText().toString();
if(val.isEmpty() || val.isBlank()) return false; if(val1.isEmpty() || val1.isBlank()) return false;
((paramNumber) t).val = Integer.parseInt(val); ((paramNumber) t).val = Integer.parseInt(val1);
break; break;
case paramString: case paramString:
val = ((EditText) v).getText().toString(); String val2 = ((EditText) v).getText().toString();
//if(val.isEmpty() || val.isBlank()) return false; //if(val.isEmpty() || val.isBlank()) return false;
((paramString) t).val = val; ((paramString) t).val = val2;
break; break;
case paramStringArray: case paramStringArray:
val = ((EditText) v).getText().toString(); String val3 = ((EditText) v).getText().toString();
if(val.isEmpty() || val.isBlank()) return false; if(val3.isEmpty() || val3.isBlank()) return false;
((paramStringArray) t).val = val.split("\n"); ((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; break;
} }
} catch (Exception e) { } catch (Exception e) {
@@ -397,11 +447,11 @@ public class FieldEditorHelper {
this.t = t; this.t = t;
views = new View[types.length]; views = new View[types.length];
for(int i = 0; i < types.length; i++){ for(int i = 0; i < types.length; i++){
TextView tv = new TextView(c);
tv.setText(types[i].name); parentView.addView(new TextViewBuilder(c, types[i].name)
tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); .align_center()
tv.setTextSize(20); .size(20)
parentView.addView(tv); .build());
views[i] = createEdit(c, types[i]); views[i] = createEdit(c, types[i]);
parentView.addView(views[i]); parentView.addView(views[i]);
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -20,6 +21,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import com.google.android.material.button.MaterialButton;
import com.ridgebotics.ridgescout.MainActivity; import com.ridgebotics.ridgescout.MainActivity;
import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.databinding.FragmentSettingsFieldsBinding; import com.ridgebotics.ridgescout.databinding.FragmentSettingsFieldsBinding;
@@ -28,6 +30,7 @@ import com.ridgebotics.ridgescout.types.input.FieldType;
import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView; import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
import com.ridgebotics.ridgescout.ui.views.FieldDisplay; import com.ridgebotics.ridgescout.ui.views.FieldDisplay;
import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -194,10 +197,9 @@ public class FieldsFragment extends Fragment {
sv.addView(table); 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); FieldEditorHelper f = new FieldEditorHelper(getContext(), field, table);
@@ -215,7 +217,7 @@ public class FieldsFragment extends Fragment {
AlertDialog dialog = alert.create(); AlertDialog dialog = alert.create();
dialog.show(); dialog.show();
Button deleteButton = new Button(getContext()); MaterialButton deleteButton = new MaterialButton(getContext());
deleteButton.setText("DELETE"); deleteButton.setText("DELETE");
deleteButton.setOnClickListener(l -> { deleteButton.setOnClickListener(l -> {
AlertDialog.Builder alert2 = new AlertDialog.Builder(getContext()); AlertDialog.Builder alert2 = new AlertDialog.Builder(getContext());
@@ -270,7 +272,7 @@ public class FieldsFragment extends Fragment {
System.arraycopy(currentValues, 0, newValues, 0, currentValues.length); System.arraycopy(currentValues, 0, newValues, 0, currentValues.length);
System.out.println("Length: " + values.size()); Log.i(getClass().toString(), "Length: " + values.size());
newValues[currentValues.length] = new FieldType[values.size()]; newValues[currentValues.length] = new FieldType[values.size()];
for(int i = 0; i < values.size(); i++) { for(int i = 0; i < values.size(); i++) {
@@ -1,6 +1,7 @@
package com.ridgebotics.ridgescout.ui.settings; 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 androidx.navigation.fragment.FragmentKt.findNavController;
import static com.ridgebotics.ridgescout.utility.SettingsManager.AllyPosKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.AllyPosKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.CustomEventsKey; 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.WifiModeKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.YearNumKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.YearNumKey;
import static com.ridgebotics.ridgescout.utility.SettingsManager.defaults; 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.getEditor;
import static com.ridgebotics.ridgescout.utility.SettingsManager.prefs; import static com.ridgebotics.ridgescout.utility.SettingsManager.prefs;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
@@ -26,7 +31,6 @@ import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
@@ -36,7 +40,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.checkbox.MaterialCheckBox; 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.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.R;
@@ -44,8 +50,11 @@ import com.ridgebotics.ridgescout.databinding.FragmentSettingsBinding;
import com.ridgebotics.ridgescout.scoutingData.Fields; import com.ridgebotics.ridgescout.scoutingData.Fields;
import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView; import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView;
import com.ridgebotics.ridgescout.ui.views.TallyCounterView; import com.ridgebotics.ridgescout.ui.views.TallyCounterView;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.ToDelete;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -64,20 +73,6 @@ public class SettingsFragment extends Fragment {
reloadSettings(); 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; return root;
@@ -90,25 +85,73 @@ public class SettingsFragment extends Fragment {
SettingsManager manager = new SettingsManager(getContext()); 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")); manager.addItem(new CheckboxSettingsItem(CustomEventsKey, "Custom Events"));
StringSettingsItem FTPServer = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPServer, "FTP Server (Sync)"); CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "[⚠] Send meta files");
manager.addItem(FTPServer);
CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "Sync meta files");
manager.addItem(FTPSendMetaFiles); manager.addItem(FTPSendMetaFiles);
CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "FTP Enabled", FTPServer, 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 FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "Sync Enabled", FTPServer, FTPKey, FTPSendMetaFiles);
manager.addItem(FTPEnabled); manager.addItem(FTPEnabled);
manager.addItem(new CheckboxSettingsItem(WifiModeKey, "Wifi Mode", 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 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)); manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list));
@@ -131,16 +174,48 @@ public class SettingsFragment extends Fragment {
manager.addItem(new StringSettingsItem(UnameKey, "Username")); manager.addItem(new StringSettingsItem(UnameKey, "Username"));
manager.addItem(new NumberSettingsItem(TeamNumKey, "Team Number", 0, 99999)); manager.addItem(new NumberSettingsItem(TeamNumKey, "Team Number", 0, 99999));
manager.addItem(new HeaderSettingsItem("Scouting"));
binding.SettingsTable.removeAllViews(); binding.SettingsTable.removeAllViews();
manager.getView(binding.SettingsTable); manager.getView(binding.SettingsTable);
if(!DataManager.getevcode().equals("unset")){
Button editNoticeButton = new Button(getContext()); // Add "Edit scout notice" button to the bottom of the page=
editNoticeButton.setText("Edit Scout Notice");
binding.SettingsTable.addView(editNoticeButton);
editNoticeButton.setOnClickListener(v->editNotice()); // 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);
} }
@@ -175,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();
}
@@ -281,9 +391,8 @@ public class SettingsFragment extends Fragment {
@Override @Override
public View createView(Context context) { public View createView(Context context) {
TextView titleView = new TextView(context); TextView titleView = new TextViewBuilder(context, getTitle())
titleView.setText(getTitle()); .sub1().build();
titleView.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1);
TextInputLayout textInputLayout = new TextInputLayout(context); TextInputLayout textInputLayout = new TextInputLayout(context);
editText = new TextInputEditText(context); editText = new TextInputEditText(context);
@@ -344,7 +453,7 @@ public class SettingsFragment extends Fragment {
@Override @Override
public View createView(Context context) { public View createView(Context context) {
LinearLayout ll = new LinearLayout(getContext()); LinearLayout ll = new LinearLayout(getContext());
ll.setOrientation(LinearLayout.VERTICAL); ll.setOrientation(VERTICAL);
tally = new TallyCounterView(getContext()); tally = new TallyCounterView(getContext());
@@ -363,11 +472,10 @@ public class SettingsFragment extends Fragment {
}); });
tally.setEnabled(enabled); tally.setEnabled(enabled);
TextView tv = new TextView(getContext());
tv.setText(getTitle()); ll.addView(new TextViewBuilder(getContext(), getTitle())
tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6); .h6()
tv.setGravity(Gravity.CENTER); .build());
ll.addView(tv);
ll.addView(tally); ll.addView(tally);
@@ -472,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 { public class SettingsManager {
private Context context; private Context context;
private HashMap<String, Object> settings; private HashMap<String, Object> settings;
@@ -494,7 +689,7 @@ public class SettingsFragment extends Fragment {
items.add(item); items.add(item);
LinearLayout itemContainer = new LinearLayout(context); LinearLayout itemContainer = new LinearLayout(context);
itemContainer.setOrientation(LinearLayout.VERTICAL); itemContainer.setOrientation(VERTICAL);
itemContainer.setPadding(32, 0, 32, 8); itemContainer.setPadding(32, 0, 32, 8);
View view = item.createView(context); View view = item.createView(context);
@@ -1,269 +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 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;
// 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);
System.out.println("Uploaded" + localFile.getName());
setLocalFileTimestamp(localFile, curSyncTime);
remoteTimestamps.put(localFile.getName(), curSyncTime);
upCount++;
}else{
System.out.println("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);
System.out.println("Downloaded " + localFile.getName());
if(!localFile.exists()) System.out.println("Not exist");
else if(after(remoteTimestamp, localTimeStamp)) System.out.println("Before: " + (localTimeStamp.getTime()-remoteTimestamp.getTime()));
// Date d = getUtcTimestamp(remoteFile);
setLocalFileTimestamp(localFile, remoteTimestamps.get(localFile.getName()));
// remoteTimestamps.put(remoteFile, curSyncTime);
downCount++;
}else{
System.out.println("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.ByteBuilder;
import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.DataManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -49,7 +50,10 @@ public class FileSelectorFragment extends Fragment {
meta_string_array = new String[]{ meta_string_array = new String[]{
"matches.fields", "matches.fields",
"pits.fields", "pits.fields",
evcode+".eventdata" evcode+".eventdata",
evcode+".rescout",
evcode+".scoutnotice",
"todelete.colabarray",
}; };
String[] files = FileEditor.getEventFiles(evcode); String[] files = FileEditor.getEventFiles(evcode);
@@ -74,10 +78,12 @@ public class FileSelectorFragment extends Fragment {
checkBox.setChecked(true); checkBox.setChecked(true);
tr.addView(checkBox); tr.addView(checkBox);
TextView tv = new TextView(getContext()); // Filename
tv.setText(String.valueOf(files[i])); tr.addView(
tv.setTextSize(20); new TextViewBuilder(getContext(), files[i])
tr.addView(tv); .size(20)
.build()
);
final int fi = i; final int fi = i;
tr.setOnClickListener(view -> { tr.setOnClickListener(view -> {
@@ -0,0 +1,385 @@
package com.ridgebotics.ridgescout.ui.transfer;
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.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;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
// Class to synchronise data over HTTP.
public class HttpSync extends Thread {
public static final String timestampsFilename = "timestamps";
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();
HttpSync sync = new HttpSync();
sync.start();
}
private int upCount = 0;
private int downCount = 0;
private class TransferFile {
public String filename;
public Date updated;
public String checksum;
}
private List<TransferFile> localFiles = new ArrayList<>();
private List<TransferFile> remoteFiles = new ArrayList<>();
private void await() {
while(!runningRequest.get()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
AtomicBoolean runningRequest = new AtomicBoolean(false);
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();
setUpdateIndicator("Getting Metadata...");
// Load metadata from server
getRemoteFileMetadata(serverIP, serverKey);
if(!isRunning){
setUpdateIndicator("Error Connecting");
onResult.onResult(true, upCount, downCount);
return;
}
getLocalFileMetadata();
localFiles.removeIf(localFile -> removeFiles.contains(localFile.filename+","+localFile.checksum));
remoteFiles.removeIf(remoteFile -> removeFiles.contains(remoteFile.filename+","+remoteFile.checksum));
// Wait for metadata request to finish
setUpdateIndicator("Uploading 0%");
for(int i = 0; i < localFiles.size(); i++){
TransferFile localFile = localFiles.get(i);
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 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);
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 {
Log.d(getClass().toString(), "LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": Not uploaded");
}
setUpdateIndicator("Uploading " + (Math.floor((double) (i * 1000) / localFiles.size()) / 10) + "%");
}
setUpdateIndicator("Downloading 0%");
for(int i = 0; i < remoteFiles.size(); i++){
TransferFile remoteFile = remoteFiles.get(i);
TransferFile localFile = findInFileArray(localFiles, remoteFile.filename);
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");
downCount++;
} else {
Log.d(getClass().toString(), "RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": Not downloaded");
}
setUpdateIndicator("Downloading " + (Math.floor((double) (i * 1000) / remoteFiles.size()) / 10) + "%");
}
// Remove files marked for deletion
ToDelete.deleteFiles();
setUpdateIndicator("Finished, " + upCount + " Up, " + downCount + " Down");
onResult.onResult(false, upCount, downCount);
isRunning = false;
}
// Find file based off of filename
private TransferFile findInFileArray(List<TransferFile> files, String filename){
for(TransferFile file : files) {
if(file.filename.equals(filename))
return file;
}
return null;
}
// Get teh last modified date of a file
private Date getLocalFileUtcTimestamp(File file) {
return new Date(file.lastModified());
}
// Load the local metadata of files
private void getLocalFileMetadata() {
File localDir = new File(baseDir);
File[] localFileNames = localDir.listFiles();
assert localFileNames != null;
for (int i = 0; i < localFileNames.length; i++) {
File file = localFileNames[i];
if(file.isDirectory()) continue;
// Remove timestamts file
if(file.getName().equals(timestampsFilename)) continue;
TransferFile tf = new TransferFile();
tf.filename = file.getName();
tf.updated = getLocalFileUtcTimestamp(file);
try {
tf.checksum = FileEditor.getSHA256Hash(file.getName());
} catch (Exception e) {
AlertManager.error("Failed to get hash of: " + file.getName(), e);
continue;
}
localFiles.add(tf);
}
}
// Send request to server and retrieve metadata
private void getRemoteFileMetadata(String serverURL, String serverKey) {
final RequestTask rq = new RequestTask();
runningRequest.set(false);
rq.onResult(metadata -> {
try {
JSONObject j = new JSONObject(metadata);
for (Iterator<String> it = j.keys(); it.hasNext(); ) {
String key = it.next();
JSONObject obj = j.getJSONObject(key);
TransferFile tf = new TransferFile();
tf.filename = key;
tf.updated = new Date(Long.parseLong(obj.getString("modified")));
tf.checksum = obj.getString("sha256");
remoteFiles.add(tf);
}
}catch(JSONException | NullPointerException e ) {
AlertManager.error(e);
isRunning = false;
}
runningRequest.set(true);
return null;
});
rq.execute((serverURL + "/api/metadata"), "api_key: " + serverKey);
await();
}
// Create HTTP request to upload file
void uploadFile(TransferFile tf, String serverURL, String apiKey, boolean special) {
runningRequest.set(false);
// 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
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, (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();
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 static com.ridgebotics.ridgescout.utility.FileEditor.TBAHeader;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -22,6 +26,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.button.MaterialButton;
import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.databinding.FragmentTransferTbaBinding; import com.ridgebotics.ridgescout.databinding.FragmentTransferTbaBinding;
import com.ridgebotics.ridgescout.types.frcEvent; 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.RequestTask;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@@ -40,6 +46,7 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Function;
// Class to download data from a specific event and encode it. // Class to download data from a specific event and encode it.
public class TBAEventFragment extends Fragment { public class TBAEventFragment extends Fragment {
@@ -49,8 +56,6 @@ public class TBAEventFragment extends Fragment {
private final int year = SettingsManager.getYearNum(); private final int year = SettingsManager.getYearNum();
private ProgressDialog loadingDialog;
private static JSONObject eventData = null; private static JSONObject eventData = null;
public static void setEventData(JSONObject j){ public static void setEventData(JSONObject j){
eventData = j; eventData = j;
@@ -72,27 +77,18 @@ public class TBAEventFragment extends Fragment {
Table = binding.matchTable; 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.setStretchAllColumns(true);
Table.bringToFront(); Table.bringToFront();
TableRow tr1 = new TableRow(getContext());
addTableText(tr1, "Downloading Teams...");
Table.addView(tr1);
final RequestTask rq = new RequestTask(); final RequestTask rq = new RequestTask();
rq.onResult(teamsStr -> { rq.onResult(teamsStr -> {
TableRow tr11 = new TableRow(getContext());
addTableText(tr11, "Downloading Matches...");
Table.addView(tr11);
final RequestTask rq1 = new RequestTask(); final RequestTask rq1 = new RequestTask();
rq1.onResult(matchesStr -> { rq1.onResult(matchesStr -> {
matchTable(matchesStr, teamsStr, eventData); matchTable(matchesStr, teamsStr, eventData);
stopLoading(); AlertManager.stopLoading();
return null; return null;
}); });
rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader); rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader);
@@ -104,18 +100,13 @@ public class TBAEventFragment extends Fragment {
} }
private void addTableText(TableRow tr, String textStr){ private void addTableText(TableRow tr, String textStr){
TextView text = new TextView(getContext()); tr.addView(new TextViewBuilder(getContext(), textStr)
text.setTextSize(18); .size(18)
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center // .align_center()
text.setText(textStr); .build());
tr.addView(text);
} }
public void matchTable(String matchesString, String teamsString, JSONObject eventData){ public void matchTable(String matchesString, String teamsString, JSONObject eventData){
Table.removeAllViews();
Table.setStretchAllColumns(true);
Table.bringToFront();
try { try {
final JSONArray matchData = new JSONArray(matchesString); final JSONArray matchData = new JSONArray(matchesString);
// final JSONArray matchData = new JSONArray(); // final JSONArray matchData = new JSONArray();
@@ -130,30 +121,19 @@ public class TBAEventFragment extends Fragment {
} }
// Event code at top // Event code at top
TextView tv = new TextView(getContext()); Table.addView(new TextViewBuilder(getContext(), matchKey)
tv.setLayoutParams(new TableRow.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(18)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setText(matchKey);
tv.setTextSize(18);
Table.addView(tv);
// Event Name // Event Name
tv = new TextView(getContext()); Table.addView(new TextViewBuilder(getContext(), matchName)
tv.setLayoutParams(new TableRow.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(28)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText(matchName);
tv.setTextSize(28);
Table.addView(tv);
// Save button // Save button
Button btn = new Button(getContext()); MaterialButton btn = new MaterialButton(getContext());
btn.setText("Save"); btn.setText("Save");
btn.setTextSize(18); btn.setTextSize(18);
btn.setLayoutParams(new TableRow.LayoutParams( 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){ if(teamData.length() == 0){
tv = new TextView(getContext()); Table.addView(new TextViewBuilder(getContext(), "This event has no teams released yet...")
tv.setLayoutParams(new TableRow.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(18)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
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);
btn.setVisibility(View.GONE); btn.setVisibility(View.GONE);
return; return;
}else if(matchData.length() == 0){ }else if(matchData.length() == 0){
tv = new TextView(getContext()); Table.addView(new TextViewBuilder(getContext(), "This event has no matches released yet...")
tv.setLayoutParams(new TableRow.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(18)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("This event has no matches released yet...");
tv.setTextSize(18);
Table.addView(tv);
tv = new TextView(getContext()); Table.addView(new TextViewBuilder(getContext(), "Try manually adding practice matches.")
tv.setLayoutParams(new TableRow.LayoutParams( .align_center()
ViewGroup.LayoutParams.MATCH_PARENT, .size(18)
ViewGroup.LayoutParams.WRAP_CONTENT .build());
));
tv.setGravity(Gravity.CENTER_HORIZONTAL);
tv.setText("Try manually adding practice matches.");
tv.setTextSize(18);
Table.addView(tv);
} }
tv = new TextView(getContext()); Table.addView(
tv.setLayoutParams(new TableRow.LayoutParams( new TextViewBuilder(getContext(), "Teams")
ViewGroup.LayoutParams.MATCH_PARENT, .align_center()
ViewGroup.LayoutParams.WRAP_CONTENT .size(28)
)); .build()
tv.setGravity(Gravity.CENTER_HORIZONTAL); );
tv.setText("Teams");
tv.setTextSize(28);
Table.addView(tv);
// Sort the teams into numerical order
int[] teams = new int[teamData.length()]; int[] teams = new int[teamData.length()];
for(int i = 0 ; i < teamData.length(); i++){ for(int i = 0 ; i < teamData.length(); i++){
@@ -235,28 +189,26 @@ public class TBAEventFragment extends Fragment {
Arrays.sort(teams); Arrays.sort(teams);
// Loop through each match
TableRow tr = null; TableRow tr = null;
for(int i=0; i < teamData.length(); i++){ for(int i=0; i < teamData.length(); i++){
// frcTeam team = event.teams.get(i);
int num = teams[i]; int num = teams[i];
// If this is every 7th row, add the new row.
if(i % 7 == 0){ if(i % 7 == 0){
if(i != 0) if(i != 0)
Table.addView(tr); Table.addView(tr);
tr = new TableRow(getContext()); tr = new TableRow(getContext());
} }
TextView text = new TextView(getContext());
text.setTextSize(18);
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
text.setText(String.valueOf(num)); tr.addView(
// if(fileEditor.fileExist(event.eventCode + "-" + num + ".pitscoutdata")){ new TextViewBuilder(getContext(), String.valueOf(num))
// text.setBackgroundColor(0x3000FF00); .align_center()
// }else{ .size(18)
// text.setBackgroundColor(0x30FF0000); .build()
// } );
tr.addView(text);
} }
if(tr != null) if(tr != null)
Table.addView(tr); Table.addView(tr);
@@ -269,19 +221,13 @@ public class TBAEventFragment extends Fragment {
tv = new TextView(getContext()); Table.addView(
tv.setLayoutParams(new TableRow.LayoutParams( new TextViewBuilder(getContext(), "Matches")
ViewGroup.LayoutParams.MATCH_PARENT, .align_center()
ViewGroup.LayoutParams.WRAP_CONTENT .size(28)
)); .build()
tv.setGravity(Gravity.CENTER_HORIZONTAL); );
tv.setText("Matches");
tv.setTextSize(28);
Table.addView(tv);
tr = new TableRow(getContext()); tr = new TableRow(getContext());
addTableText(tr, "#"); addTableText(tr, "#");
addTableText(tr, "Red-1"); addTableText(tr, "Red-1");
@@ -335,22 +281,24 @@ public class TBAEventFragment extends Fragment {
int[] redKeys = new int[3]; int[] redKeys = new int[3];
for(int b=0;b<6;b++){ for(int b=0;b<6;b++){
TextView text = new TextView(getContext()); TextViewBuilder text = new TextViewBuilder(getContext())
text.setTextSize(18); .size(18)
text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center .align_center();
tr.addView(text);
if(b < 3){ if(b < 3){
String str = redAlliance.getString(b).substring(3); String str = redAlliance.getString(b).substring(3);
redKeys[b] = Integer.parseInt(str); redKeys[b] = Integer.parseInt(str);
text.setText(str); text.text(str);
text.setBackgroundColor(tba_red); text.tv.setBackgroundColor(tba_red);
}else{ }else{
String str = blueAlliance.getString(b-3).substring(3); String str = blueAlliance.getString(b-3).substring(3);
blueKeys[b-3] = Integer.parseInt(str); blueKeys[b-3] = Integer.parseInt(str);
text.setText(str); text.text(str);
text.setBackgroundColor(tba_blue); text.tv.setBackgroundColor(tba_blue);
} }
tr.addView(text.build());
} }
Table.addView(tr); Table.addView(tr);
@@ -365,22 +313,14 @@ public class TBAEventFragment extends Fragment {
toggle = !toggle; toggle = !toggle;
} }
// btn.setOnClickListener(v -> {
// if(saveData(matchesOBJ, teamData, eventData)){
// alert("Info", "Saved!");
// }else{
// alert("Error", "Error saving files.");
// }
// });
}catch (JSONException j){ }catch (JSONException j){
AlertManager.error("Failed Downloading", j); AlertManager.error("Failed Downloading", j);
stopLoading(); AlertManager.stopLoading();
} }
} }
private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){ private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){
startLoading("Saving data..."); AlertManager.startLoading("Downloading team data...");
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
try { try {
@@ -405,16 +345,44 @@ public class TBAEventFragment extends Fragment {
teamObj.country = team.getString("country"); teamObj.country = team.getString("country");
teamObj.startingYear = team.getInt("rookie_year"); teamObj.startingYear = team.getInt("rookie_year");
ImageRequestTask imageRequestTask = new ImageRequestTask();
imageRequestTask.onResult(bitmap -> { RequestTask rq = new RequestTask();
teamObj.bitmap = bitmap; rq.onResult(s -> {
teamObj.teamColor = frcTeam.findPrimaryColor(bitmap); try {
teams.add(teamObj); 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; 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()) { while (teams.size() != teamData.length()) {
@@ -431,31 +399,15 @@ public class TBAEventFragment extends Fragment {
AlertManager.toast("Saved!"); AlertManager.toast("Saved!");
getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer)); getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer));
stopLoading(); AlertManager.stopLoading();
}catch(Exception j) { }catch(Exception j) {
AlertManager.error(j); AlertManager.error(j);
stopLoading(); AlertManager.stopLoading();
} }
}); });
t.start(); t.start();
return false; 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.AlertManager;
import com.ridgebotics.ridgescout.utility.RequestTask; import com.ridgebotics.ridgescout.utility.RequestTask;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@@ -54,12 +55,6 @@ public class TBASelectorFragment extends Fragment {
Table = binding.matchTable; Table = binding.matchTable;
Table.setStretchAllColumns(true);
TableRow tr = new TableRow(getContext());
addTableText(tr, "Loading Events...");
Table.addView(tr);
startLoading("Loading Events..."); startLoading("Loading Events...");
final RequestTask rq = new RequestTask(); final RequestTask rq = new RequestTask();
@@ -77,14 +72,6 @@ public class TBASelectorFragment extends Fragment {
return binding.getRoot(); 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){ public static int getEventTypeWeight(String type){
switch(type){ switch(type){
case "Preseason": return -3; case "Preseason": return -3;
@@ -103,7 +90,7 @@ public class TBASelectorFragment extends Fragment {
public void eventTable(String dataString){ public void eventTable(String dataString){
Table.removeAllViews(); Table.removeAllViews();
Table.setStretchAllColumns(true); // Table.setStretchAllColumns(true);
Table.bringToFront(); Table.bringToFront();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
@@ -65,13 +65,13 @@ public class TransferFragment extends Fragment {
binding.SyncButton.setOnClickListener(v -> { binding.SyncButton.setOnClickListener(v -> {
binding.SyncButton.setEnabled(false); binding.SyncButton.setEnabled(false);
FTPSync.sync(); HttpSync.sync();
}); });
if(FTPSync.getIsRunning()) if(HttpSync.getIsRunning())
binding.SyncButton.setEnabled(false); binding.SyncButton.setEnabled(false);
FTPSync.setOnResult((error, upcount, downcount) -> { HttpSync.setOnResult((error, upcount, downcount) -> {
if (getActivity() != null) if (getActivity() != null)
getActivity().runOnUiThread(() -> { getActivity().runOnUiThread(() -> {
binding.SyncButton.setEnabled(true); binding.SyncButton.setEnabled(true);
@@ -79,8 +79,8 @@ public class TransferFragment extends Fragment {
}); });
}); });
binding.syncIndicator.setText(FTPSync.text); binding.syncIndicator.setText(HttpSync.text);
FTPSync.setOnUpdateIndicator(text -> {if(getActivity() != null) getActivity().runOnUiThread(() -> binding.syncIndicator.setText(text));}); HttpSync.setOnUpdateIndicator(text -> {if(getActivity() != null) getActivity().runOnUiThread(() -> binding.syncIndicator.setText(text));});
if(evcode.equals("unset")){ if(evcode.equals("unset")){
binding.noEventError.setVisibility(View.VISIBLE); binding.noEventError.setVisibility(View.VISIBLE);
@@ -8,6 +8,7 @@ import android.bluetooth.BluetoothSocket;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.util.Log;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@@ -139,7 +140,7 @@ public class BluetoothReceiver {
} catch (IOException e) { } catch (IOException e) {
if (e.getMessage() != null && e.getMessage().contains("bt socket closed, read return: -1")) { if (e.getMessage() != null && e.getMessage().contains("bt socket closed, read return: -1")) {
receiveddata.onConnectionStop(); receiveddata.onConnectionStop();
System.out.println("Bluetooth socket closed, treating as end of stream"); Log.i(getClass().toString(), "Bluetooth socket closed, treating as end of stream");
} else { } else {
throw e; throw e;
} }
@@ -1,6 +1,7 @@
package com.ridgebotics.ridgescout.ui.transfer.bluetooth; package com.ridgebotics.ridgescout.ui.transfer.bluetooth;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -117,7 +118,7 @@ public class BluetoothReceiverFragment extends Fragment {
private void receiveData(byte[] data, int bytes) { private void receiveData(byte[] data, int bytes) {
byte[] newBytes = FileEditor.getByteBlock(data, 0, bytes); byte[] newBytes = FileEditor.getByteBlock(data, 0, bytes);
System.out.println("Recieved " + bytes + " Bytes over bluetooth!"); Log.i(getClass().toString(), "Recieved " + bytes + " Bytes over bluetooth!");
recievedBytes.add(newBytes); recievedBytes.add(newBytes);
} }
@@ -138,7 +139,7 @@ public class BluetoothReceiverFragment extends Fragment {
ScoutingFile f = ScoutingFile.decode((byte[]) result.get(i).get()); ScoutingFile f = ScoutingFile.decode((byte[]) result.get(i).get());
if (f != null) { if (f != null) {
System.out.println(f.filename); Log.i(getClass().toString(), f.filename);
if (f.write()) if (f.write())
result_filenames += f.filename + "\n"; result_filenames += f.filename + "\n";
} }
@@ -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; package com.ridgebotics.ridgescout.ui.transfer.codes;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -18,16 +19,11 @@ import androidx.fragment.app.Fragment;
import com.ridgebotics.ridgescout.databinding.FragmentTransferCodeSenderBinding; import com.ridgebotics.ridgescout.databinding.FragmentTransferCodeSenderBinding;
import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.google.zxing.BarcodeFormat; import com.ridgebotics.ridgescout.utility.TaskRunner;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap; import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
// Class to show the code transfer thing. // Class to show the code transfer thing.
@@ -38,23 +34,28 @@ public class CodeGeneratorView extends Fragment {
private TextView qrIndexN; private TextView qrIndexN;
private TextView qrIndexD; 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 static final int maxQrSize = 800;
private final int minQrSpeed = 300 + maxQrSpeed - 1;
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 int minQrSize = 0;
private final int maxQrSize = 800;
private int qrSize = 200; private int qrSize = 200;
private final int defaultQrDelay = 419; private double qrDelay = 0;
private int qrDelay = 0;
private int qrIndex = 0; private int qrIndex = 0;
private CountDownTimer timer; private CountDownTimer timer;
private int qrCount = 0; private int qrCount = 0;
private ArrayList<Bitmap> qrBitmaps; private List<Bitmap> qrBitmaps = new ArrayList<>();
private FragmentTransferCodeSenderBinding binding; private FragmentTransferCodeSenderBinding binding;
@@ -77,6 +78,12 @@ public class CodeGeneratorView extends Fragment {
qrIndexN = binding.qrIndexN; qrIndexN = binding.qrIndexN;
qrIndexD = binding.qrIndexD; 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); String compressed = new String(FileEditor.blockCompress(data, FileEditor.lengthHeaderBytes), StandardCharsets.ISO_8859_1);
if(compressed.isEmpty()){ if(compressed.isEmpty()){
@@ -85,75 +92,30 @@ public class CodeGeneratorView extends Fragment {
} }
minQrSize = Math.round((float)compressed.length() / maxQrCount)+1; minQrSize = Math.round((float)compressed.length() / maxQrCount)+1;
qrSize += minQrSize;
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSpeedSlider.setMax((minQrSpeed-maxQrSpeed)*2);
qrSizeSlider.setProgress(minQrSize+qrSize);
qrSpeedSlider.setProgress(defaultQrDelay+5);
sendData(compressed); sendData(compressed);
qrSpeedSlider.setMax(maxQrSpeed*2);
qrSpeedSlider.setProgress(maxQrSpeed + defaultQrDelay);
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSizeSlider.setProgress(qrSize-minQrSize);
startLoop();
return binding.getRoot(); 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){ private void sendData(String data){
qrCount = (data.length()/qrSize)+1; qrCount = (data.length()/qrSize)+1;
qrIndexD.setText(String.valueOf(qrCount)); qrIndexD.setText(String.valueOf(qrCount));
// alert("size", ""+binding.qrSizeSlider.getProgress()+"\n"+binding.qrSizeSlider.getMax());
qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
qrDelay = -(minQrSpeed - progress - maxQrSpeed + 1); qrDelay = ((double) progress /maxQrSpeed) - 1;
// startLoop();
} }
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) {} public void onStartTrackingTouch(SeekBar seekBar) {}
@@ -173,43 +135,24 @@ public class CodeGeneratorView extends Fragment {
qrCount = ((data.length()+1)/qrSize) +1; qrCount = ((data.length()+1)/qrSize) +1;
qrIndexD.setText(String.valueOf(qrCount)); qrIndexD.setText(String.valueOf(qrCount));
sendData(data); 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(){ private void updateQr(){
if(qrBitmaps.isEmpty())
return;
qrImage.setImageBitmap(qrBitmaps.get(qrIndex)); qrImage.setImageBitmap(qrBitmaps.get(qrIndex));
if(qrDelay > 0) { if(qrDelay > 0) {
this.qrIndex += 1; this.qrIndex += 1;
@@ -226,13 +169,36 @@ public class CodeGeneratorView extends Fragment {
qrIndexN.setText(String.valueOf(qrIndex+1)); qrIndexN.setText(String.valueOf(qrIndex+1));
} }
private void qrLoop(){ private boolean shouldstop = false;
timer = new CountDownTimer(minQrSpeed-Math.abs(qrDelay)+1, 1000) {
public void onTick(long millisUntilFinished) {} private void startLoop() {
public void onFinish() { final Handler handler = new Handler();
updateQr(); Runnable runnable = new Runnable() {
qrLoop();
@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){ if(barColors != null){
final double width = getWidth()/barColors.length; final double width = (double) getWidth() /barColors.length;
final int top = 0; final int top = 0;
final int bottom = barHeight; final int bottom = barHeight;
@@ -14,19 +14,69 @@ import com.google.zxing.Reader;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.datamatrix.DataMatrixReader; import com.google.zxing.datamatrix.DataMatrixReader;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function; import java.util.function.Function;
// Background task for code scanning, to not slow down the scanner. // Background task for code scanning, to not slow down the scanner.
public class CodeScanTask extends AsyncTask<String, String, String>{ //public class CodeScanTask extends AsyncTask<String, String, String>{
private Function<String, String> resultFunction = null; // 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; private Bitmap image;
public CodeScanTask(Bitmap image) {
this.image = image;
}
@Override @Override
protected String doInBackground(String... str) { public String call() {
if(image == null){return null;} if(image == null){return null;}
int width = image.getWidth(); int width = image.getWidth();
@@ -52,17 +102,4 @@ public class CodeScanTask extends AsyncTask<String, String, String>{
return null; 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.app.AlertDialog;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.Image; import android.media.Image;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; 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.BuiltByteParser;
import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.FileEditor;
import com.google.common.util.concurrent.ListenableFuture; 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.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -62,21 +67,12 @@ public class CodeScannerView extends Fragment {
alert.create().show(); alert.create().show();
} }
private float scale = 0;
private final int downscale = 1; private final int downscale = 1;
private LifecycleOwner lifecycle; private LifecycleOwner lifecycle;
private void setImage(Bitmap bmp){ 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); scanQRCode(bmp);
binding.scannerImage.setImageBitmap(bmp); binding.scannerImage.setImageBitmap(bmp);
binding.scannerThreshold.bringToFront();
// alert("test", getChildCount()+"");
} }
// private Bitmap img // private Bitmap img
@@ -99,37 +95,38 @@ public class CodeScannerView extends Fragment {
final int height = image.getHeight(); final int height = image.getHeight();
int[] pixels = new int[width * height]; int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) { for (int i = 0; i < width*height; i++) {
for (int x = 0; x < width; x++) { int L = levelMap[yBuffer.get(i) & 0xff];
int L = levelMap[yBuffer.get() & 0xff]; pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L;
pixels[y * width + x] = 0xff000000 | (L << 16) | (L << 8) | L;
}
} }
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height); bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
// Bitmap.rota
return bitmap; return bitmap;
} }
public void scanQRCode(Bitmap bitmap) { public void scanQRCode(Bitmap bitmap) {
CodeScanTask async = new CodeScanTask(); // CodeScanTask async = new CodeScanTask();
async.setImage(bitmap); new TaskRunner().executeAsync(new CodeScanTask(bitmap), data -> {
async.onResult(data -> {
if(data != null){ if(data != null){
// alert("test", ""+fileEditor.byteFromChar(data.charAt(0))); // alert("test", ""+fileEditor.byteFromChar(data.charAt(0)));
compileData( compileData(
FileEditor.byteFromChar(data.charAt(0)), FileEditor.byteFromChar(data.charAt(0)),
FileEditor.byteFromChar(data.charAt(1)), FileEditor.byteFromChar(data.charAt(1)),
FileEditor.byteFromChar(data.charAt(2)), FileEditor.byteFromChar(data.charAt(2)),
(FileEditor.byteFromChar(data.charAt(3))+1), (FileEditor.byteFromChar(data.charAt(3))+1),
data.substring(4) data.substring(4)
); );
} }
return null;
}); });
async.execute();
// return contents; // return contents;
@@ -231,26 +228,23 @@ public class CodeScannerView extends Fragment {
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder() Preview preview = new Preview.Builder().build();
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(Surface.ROTATION_180)
.build();
CameraSelector cameraSelector = new CameraSelector.Builder() CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK) .requireLensFacing(CameraSelector.LENS_FACING_BACK)
// .addCameraFilter(CameraFilters.NON) // .addCameraFilter(CameraFilters.NON)
.build(); .build();
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
// .setTargetResolution(new Size(224, 224)) // .setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setOutputImageRotationEnabled(false)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// .setOutputImageRotationEnabled(true)
// .setTargetRotation(Surface.ROTATION_0)
.build(); .build();
imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() { imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
@OptIn(markerClass = ExperimentalGetImage.class) @Override @OptIn(markerClass = ExperimentalGetImage.class) @Override
public void analyze(@NonNull ImageProxy image) { public void analyze(@NonNull ImageProxy image) {
@@ -269,6 +263,7 @@ public class CodeScannerView extends Fragment {
}); });
cameraProvider.unbindAll(); cameraProvider.unbindAll();
// cameraProvider.ro
cameraProvider.bindToLifecycle(lifecycle, cameraProvider.bindToLifecycle(lifecycle,
cameraSelector, imageAnalysis, preview); cameraSelector, imageAnalysis, preview);
@@ -295,6 +290,7 @@ public class CodeScannerView extends Fragment {
Log.i("title", ""+qrCount); Log.i("title", ""+qrCount);
barColors = new int[qrCount]; barColors = new int[qrCount];
prevQrIndex = qrIndex; prevQrIndex = qrIndex;
qrScannedCount = 0;
} }
final boolean updated; final boolean updated;
@@ -313,8 +309,20 @@ public class CodeScannerView extends Fragment {
if(updated && qrScannedCount >= qrCount){ 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 = ""; String compiledString = "";
for(int i=0;i<qrCount;i++){ for(int i=0;i<qrDataArr.length;i++){
compiledString += qrDataArr[i]; compiledString += qrDataArr[i];
} }
@@ -342,9 +350,8 @@ public class CodeScannerView extends Fragment {
}catch (Exception e){ }catch (Exception e){
AlertManager.error(e); AlertManager.error(e);
} }
return null;
} }
prevQrIndex = qrIndex;
} }
} }
@@ -7,6 +7,7 @@ import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.RectF; import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.View; import android.view.View;
import com.ridgebotics.ridgescout.types.data.RawDataType; import com.ridgebotics.ridgescout.types.data.RawDataType;
@@ -208,7 +209,7 @@ public class CandlestickView extends View {
upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75); upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75);
} }
System.out.println(locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax); Log.i(getClass().toString(), locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax);
setData(locmin, lowerQuartile, avg, upperQuartile, locmax, absmin, absmax); setData(locmin, lowerQuartile, avg, upperQuartile, locmax, absmin, absmax);
} }
} }
@@ -62,7 +62,7 @@ public class CustomSpinnerView extends LinearLayout {
this.index = defaultOption; this.index = defaultOption;
if(defaultOption != -1) if(defaultOption != -1)
this.item.setText(options.get(defaultOption)); this.item.setText("" + options.get(defaultOption));
AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
@@ -78,7 +78,7 @@ public class CustomSpinnerView extends LinearLayout {
CustomSpinnerPopup popup = new CustomSpinnerPopup(getContext()).init(options, option -> { CustomSpinnerPopup popup = new CustomSpinnerPopup(getContext()).init(options, option -> {
// dialog.(); // dialog.();
if(!isEnabled()) return; if(!isEnabled()) return;
item.setText(option); item.setText("" + option);
index = options.indexOf(option); index = options.indexOf(option);
if(onClickListener != null) { if(onClickListener != null) {
onClickListener.onClick(option, options.indexOf(option)); onClickListener.onClick(option, options.indexOf(option));
@@ -105,12 +105,12 @@ public class CustomSpinnerView extends LinearLayout {
} }
public void setOption(String option) { public void setOption(String option) {
item.setText(option); item.setText("" + option);
index = options.indexOf(option); index = options.indexOf(option);
} }
public void setOption(int index) { public void setOption(int index) {
item.setText(options.get(index)); item.setText("" + options.get(index));
this.index = index; this.index = index;
} }
@@ -13,6 +13,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.input.FieldposType;
import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.utility.SettingsManager;
// Custom view to display a field position. // Custom view to display a field position.
@@ -24,6 +25,8 @@ public class FieldPosView extends FrameLayout {
private ImageView imageView; private ImageView imageView;
private boolean enabled = true; private boolean enabled = true;
private boolean flip = false;
public interface onTapListener { public interface onTapListener {
void onUpdate(int[] pos); void onUpdate(int[] pos);
}; };
@@ -65,24 +68,21 @@ public class FieldPosView extends FrameLayout {
// Set touch listener // Set touch listener
setOnTouchListener((v, event) -> { setOnTouchListener((v, event) -> {
if (enabled && event.getAction() == MotionEvent.ACTION_DOWN) { if (enabled && event.getAction() == MotionEvent.ACTION_DOWN) {
x = (int) ((event.getX()/getWidth())*255); // If the field image is rotated, rotate the input and output
y = (int) ((event.getY()/getHeight())*255); 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()); tl.onUpdate(getPos());
invalidate(); invalidate();
return true; return true;
} }
return false; 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){ public void setPos(int[] pos){
@@ -96,10 +96,28 @@ public class FieldPosView extends FrameLayout {
protected void dispatchDraw(Canvas canvas) { protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas); super.dispatchDraw(canvas);
if (x >= 0 && y >= 0) { if (x >= 0 && y >= 0) {
canvas.drawCircle(
((float) x /255)*getWidth(), float cx;
((float) y /255)*getHeight(), float cy;
POINT_RADIUS, paint); // 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);
} }
} }
@@ -0,0 +1,118 @@
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;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.ridgebotics.ridgescout.R;
import com.ridgebotics.ridgescout.types.frcTeam;
import org.w3c.dom.Text;
// A view for displaying information about a team.
public class MatchScoutingIndicator extends RelativeLayout {
public MatchScoutingIndicator(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MatchScoutingIndicator(Context context) {
super(context);
init(context);
}
public TextView match_indicator_alliance_pos_text;
public TextView match_indicator_bar_team_num;
public TextView match_indicator_matchnum;
public TextView match_indicator_username;
public ImageButton match_indicator_back_button;
public ImageButton match_indicator_next_button;
private ConstraintLayout box;
private View coloredBackground;
public void init(Context context) {
LayoutInflater.from(context).inflate(R.layout.view_match_scouting_indicator, this, true);
match_indicator_back_button = findViewById(R.id.match_indicator_back_button);
match_indicator_next_button = findViewById(R.id.match_indicator_next_button);
match_indicator_alliance_pos_text = findViewById(R.id.match_indicator_alliance_pos_text);
match_indicator_username = findViewById(R.id.match_indicator_username);
match_indicator_matchnum = findViewById(R.id.match_indicator_matchnum);
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){
match_indicator_username.setText(username);
}
public void setAlliancePos(String alliancePos){
match_indicator_alliance_pos_text.setText(alliancePos);
}
public void setMatchNum(String matchNum){
match_indicator_matchnum.setText(matchNum);
}
public void setTeamNum(String teamNum){
match_indicator_bar_team_num.setText(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
);
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 android.widget.ImageView;
import com.ridgebotics.ridgescout.R; 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.ArrayList;
import java.util.List; import java.util.List;
@@ -48,9 +50,6 @@ public class MultiFieldPosView extends FrameLayout {
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageView.setAdjustViewBounds(true); imageView.setAdjustViewBounds(true);
addView(imageView); addView(imageView);
setImageResource(R.drawable.field_2025);
} }
public void addPos(int[] pos){ public void addPos(int[] pos){
@@ -62,6 +61,14 @@ public class MultiFieldPosView extends FrameLayout {
invalidate(); invalidate();
} }
public void setFieldImage(FieldposType.FieldImage image) {
if(image.flippable && SettingsManager.getFieldImageFlipped()) {
setImageResource(image.resId_flipped);
} else {
setImageResource(image.resId_normal);
}
}
@Override @Override
protected void dispatchDraw(Canvas canvas) { protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(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.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.widget.Toast; 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 { public class Colors {
// Lists and stuff // Lists and stuff
public static final int color_found = 0x7f00ff00; 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; 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 unfocused_background_color = 0x50118811;
public static final int unsaved_color = 0x60ff0000; public static final int unsaved_color = 0xffaa0000;
public static final int saved_color = 0x6000ff00; public static final int saved_color = 0xff00aa00;
public static final int rescout_color = 0x600000ff; public static final int rescout_color = 0xff007fff;
// Data graphs // Data graphs
@@ -2,13 +2,10 @@ package com.ridgebotics.ridgescout.utility;
import com.ridgebotics.ridgescout.scoutingData.Fields; import com.ridgebotics.ridgescout.scoutingData.Fields;
import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType; import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType;
import com.ridgebotics.ridgescout.types.ColabArray;
import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.frcEvent;
import com.ridgebotics.ridgescout.types.input.FieldType; 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. // Static class to hold loaded data, for ease of access.
public class DataManager { public class DataManager {
public static String evcode; 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(){ public static void reload_rescout_list(){
if(!FileEditor.fileExist(evcode + ".rescout")) {rescout_list = new ArrayList<>(); return;} String filename = evcode + ".rescout";
byte[] file = FileEditor.readFile(evcode + ".rescout"); if(!FileEditor.fileExist(filename)) {rescout_list = new ColabArray(); return;}
if(file == null) {rescout_list = new ArrayList<>(); return;} byte[] file = FileEditor.readFile(filename);
if(file == null) {rescout_list = new ColabArray(); return;}
try { try {
BuiltByteParser bbp = new BuiltByteParser(file); rescout_list = ColabArray.decode(file);
rescout_list = new ArrayList<>(Arrays.asList((String[]) (bbp.parse().get(0).get())));
} catch (Exception e){ } catch (Exception e){
AlertManager.error("Error loading scout fields", e); AlertManager.error("Error loading rescouting list", e);
rescout_list = new ArrayList<>(); rescout_list = new ColabArray();
} }
} }
public static void save_rescout_list() { public static void save_rescout_list() {
String filename = evcode + ".rescout";
try { try {
if(rescout_list.size() == 0){ FileEditor.writeFile(filename, rescout_list.encode());
FileEditor.deleteFile(evcode + ".rescout");
return;
}
ByteBuilder bb = new ByteBuilder();
bb.addStringArray(rescout_list.toArray(new String[0]));
FileEditor.writeFile(evcode + ".rescout", bb.build());
} catch (Exception e){ } catch (Exception e){
AlertManager.error("Error saving scout fields", e); AlertManager.error("Error saving rescouting list", e);
} }
} }
public static String scoutNotice = ""; public static String scoutNotice = "";
public static void reload_scout_notice(){ public static void reload_scout_notice(){
@@ -106,7 +105,7 @@ public class DataManager {
} catch (Exception e){ } catch (Exception e){
AlertManager.error("Error loading scout notice", e); AlertManager.error("Error loading scout notice", e);
rescout_list = new ArrayList<>(); scoutNotice = "";
} }
} }
@@ -1,7 +1,15 @@
package com.ridgebotics.ridgescout.utility; 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 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.frcEvent;
import com.ridgebotics.ridgescout.types.frcTeam; import com.ridgebotics.ridgescout.types.frcTeam;
@@ -15,6 +23,8 @@ import java.io.IOException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -27,18 +37,17 @@ import java.util.zip.Inflater;
// Helper class for binary editing // Helper class for binary editing
public final class FileEditor { public final class FileEditor {
@SuppressLint("SdCardPath")
public final static String baseDir = "/data/data/com.ridgebotics.ridgescout/"; public final static String baseDir = "/data/data/com.ridgebotics.ridgescout/";
public static final byte internalDataVersion = 0x01; public static final byte internalDataVersion = 0x01;
public static final int maxCompressedBlockSize = 4096; public static final int maxCompressedBlockSize = 4096;
public static final int lengthHeaderBytes = 3; public static final int lengthHeaderBytes = 3;
public static final String TBAAddress = "https://www.thebluealliance.com/api/v3/"; 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"; public static final String TBAHeader = "X-TBA-Auth-Key: tjEKSZojAU2pgbs2mBt06SKyOakVhLutj3NwuxLTxPKQPLih11aCIwRIVFXKzY4e";
// private TimeZone localTimeZone = TimeZone.getDefault();
public static String binaryVisualize(byte[] bytes){ public static String binaryVisualize(byte[] bytes){
String returnStr = ""; String returnStr = "";
@@ -100,7 +109,7 @@ public final class FileEditor {
public static int byteFromChar(char c){ 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]); return Byte.toUnsignedInt(bytes[0]);
} }
@@ -233,16 +242,19 @@ public final class FileEditor {
// } // }
public static boolean writeFile(String filepath, byte[] data) { public static boolean writeFile(String filepath, byte[] data) {
return writeFile(new File(baseDir + filepath), data);
}
public static boolean writeFile(File file, byte[] data) {
try { try {
FileOutputStream output = new FileOutputStream(baseDir + filepath); FileOutputStream output = new FileOutputStream(file.getPath());
output.write(data); output.write(data);
output.close(); output.close();
// Date d = new Date(); // Date d = new Date();
new File(baseDir + filepath).setLastModified(new Date().getTime()); file.setLastModified(new Date().getTime());
return true; return true;
} }
catch (IOException e) { catch (IOException e) {
@@ -279,10 +291,15 @@ public final class FileEditor {
} }
public static byte[] readFile(String path){ 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(); int size = (int) file.length();
byte[] bytes = new byte[size]; byte[] bytes = new byte[size];
try { try {
@@ -290,9 +307,6 @@ public final class FileEditor {
buf.read(bytes, 0, bytes.length); buf.read(bytes, 0, bytes.length);
buf.close(); buf.close();
return bytes; return bytes;
} catch (FileNotFoundException e) {
AlertManager.error(e);
return null;
} catch (IOException e) { } catch (IOException e) {
AlertManager.error(e); AlertManager.error(e);
return null; 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){ public static boolean setEvent(frcEvent event){
@@ -373,34 +410,31 @@ public final class FileEditor {
public static String[] getFiles(){
public static String[] getEventFiles(String evcode){
File f = new File(baseDir); File f = new File(baseDir);
File[] files = f.listFiles(); File[] files = f.listFiles();
if(files == null){return new String[0];} if(files == null){return new String[0];}
ArrayList<String> outFiles = new ArrayList<>(); List<String> outFiles = new ArrayList<>();
outFiles.add("matches.fields");
outFiles.add("pits.fields");
// outFiles.add(evcode + ".eventdata");
for (File file : files) { for (File file : files) {
String name = file.getName(); if (!file.isDirectory()) {
if(!file.isDirectory() && name.startsWith(evcode)) {
outFiles.add(file.getName()); outFiles.add(file.getName());
} }
} }
String[] filenames = outFiles.toArray(new String[0]); String[] filenames = outFiles.toArray(new String[0]);
try { try {
Arrays.sort(filenames, (o1, o2) -> { Arrays.sort(filenames, (o1, o2) -> {
try { try {
if (!o1.contains("-") || !o2.contains("-")) if (!o1.contains("-") || !o2.contains("-"))
return 0; return o2.compareTo(o1);
return Integer.valueOf(o1.split("-")[1]).compareTo(Integer.valueOf(o2.split("-")[1])); return Integer.valueOf(o1.split("-")[1].split("\\.")[0]).compareTo(Integer.valueOf(o2.split("-")[1].split("\\.")[0]));
} catch (Exception e) { } catch (Exception e) {
AlertManager.error(e);
return 0; return 0;
} }
}); });
@@ -412,6 +446,24 @@ public final class FileEditor {
return filenames; 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 // 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 { // public static String imageToBitMap(byte[] data) throws IOException {
// Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); // 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){ public static boolean setTeams(Context context, String key, ArrayList<frcTeam> teams){
return true; 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);
}
}
} }
@@ -0,0 +1,172 @@
package com.ridgebotics.ridgescout.utility;
import android.os.AsyncTask;
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(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) {
this.downloadUrl = downloadUrl;
this.destinationFile = destinationFile;
this.callback = callback;
}
@Override
protected File doInBackground(Void... voids) {
return run();
}
public File run() {
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
URL url = new URL(downloadUrl);
connection = (HttpURLConnection) url.openConnection();
// Configure connection for GET request
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.setConnectTimeout(30000); // 30 seconds
connection.setReadTimeout(60000); // 60 seconds
connection.connect();
// Check response code
int responseCode = connection.getResponseCode();
if (responseCode < 200 || responseCode >= 300) {
String errorResponse = readErrorResponse(connection);
errorMessage = "Download failed. Response code: " + responseCode +
(errorResponse != null ? ". Error: " + errorResponse : "");
return null;
}
// Get file size for progress tracking
long fileSize = connection.getContentLengthLong();
if (fileSize == -1) {
fileSize = connection.getContentLength(); // fallback for older API
}
inputStream = connection.getInputStream();
// Create destination file and directories if needed
if (destinationFile.getParentFile() != null && !destinationFile.getParentFile().exists()) {
if (!destinationFile.getParentFile().mkdirs()) {
errorMessage = "Failed to create destination directory: " + destinationFile.getParentFile().getAbsolutePath();
return null;
}
}
outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
long downloadedBytes = 0;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
if (isCancelled()) {
deletePartialFile();
return null;
}
outputStream.write(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// Update progress if file size is known
if (fileSize > 0) {
int progress = (int) ((downloadedBytes * 100) / fileSize);
publishProgress(progress);
}
}
outputStream.flush();
// FileEditor.writeFile(destinationFile, outputStream.toByteArray());
// Log.d(TAG, "Download successful. File saved to: " + destinationFile.getAbsolutePath());
return destinationFile;
} catch (Exception e) {
AlertManager.error(e);
errorMessage = "Download error: " + e.getMessage();
// Log.e(TAG, errorMessage, e);
deletePartialFile();
return null;
} finally {
closeResources(inputStream, outputStream, connection);
}
}
@Override
protected void onPostExecute(File result) {
if (callback != null) {
callback.onResult(outputStream, errorMessage);
}
}
@Override
protected void onCancelled() {
deletePartialFile();
if (callback != null) {
callback.onResult(null, "Download cancelled");
}
}
private String readErrorResponse(HttpURLConnection connection) {
try {
InputStream errorStream = connection.getErrorStream();
if (errorStream != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
AlertManager.error("Error reading error response", e);
}
return null;
}
private void deletePartialFile() {
if (destinationFile != null && destinationFile.exists()) {
if (destinationFile.delete()) {
AlertManager.error("Partial download file deleted");
} else {
AlertManager.error("Failed to delete partial download file");
}
}
}
private void closeResources(InputStream inputStream, OutputStream outputStream, HttpURLConnection connection) {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
AlertManager.error("Error closing input stream", e);
}
try {
if (outputStream != null) outputStream.close();
} catch (IOException e) {
AlertManager.error("Error closing output stream", e);
}
if (connection != null) {
connection.disconnect();
}
}
}
@@ -0,0 +1,167 @@
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";
public interface UploadCallback {
void onResult(String error);
}
private String uploadUrl;
private File fileToUpload;
private UploadCallback callback;
private String errorMessage;
private String[] headers;
public HttpPutFile(String uploadUrl, File fileToUpload, UploadCallback callback, String[] headers) {
this.uploadUrl = uploadUrl;
this.fileToUpload = fileToUpload;
this.callback = callback;
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;
try {
if (!fileToUpload.exists()) {
errorMessage = "File does not exist: " + fileToUpload.getAbsolutePath();
return false;
}
URL url = new URL(uploadUrl);
connection = (HttpURLConnection) url.openConnection();
// Configure connection for PUT request
connection.setRequestMethod("PUT");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "application/octet-stream");
connection.setRequestProperty("Content-Length", String.valueOf(fileToUpload.length()));
connection.setConnectTimeout(5000); // 5 seconds
connection.setReadTimeout(10000); // 10 seconds
for(int i = 0; i < headers.length; i++){
String[] split = headers[i].split(": ");
connection.setRequestProperty(split[0], split[1]);
}
connection.connect();
outputStream = connection.getOutputStream();
fileInputStream = new FileInputStream(fileToUpload);
byte[] buffer = new byte[8192];
long totalBytes = fileToUpload.length();
long uploadedBytes = 0;
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
if (isCancelled()) {
return false;
}
outputStream.write(buffer, 0, bytesRead);
uploadedBytes += bytesRead;
// Update progress
int progress = (int) ((uploadedBytes * 100) / totalBytes);
publishProgress(progress);
}
outputStream.flush();
// Check response code
int responseCode = connection.getResponseCode();
if (responseCode >= 200 && responseCode < 300) {
// Log.d(TAG, "Upload successful. Response code: " + responseCode);
return true;
} else {
// Read error response if available
String errorResponse = readErrorResponse(connection);
errorMessage = "Upload failed. Response code: " + responseCode +
(errorResponse != null ? ". Error: " + errorResponse : "");
return false;
}
} catch (Exception e) {
errorMessage = "Upload error: " + e.getMessage();
AlertManager.error(errorMessage, e);
// Log.e(TAG, errorMessage, e);
return false;
} finally {
closeResources(fileInputStream, outputStream, connection);
}
}
@Override
protected void onPostExecute(Boolean success) {
if (callback != null) {
callback.onResult(errorMessage);
}
}
@Override
protected void onCancelled() {
if (callback != null) {
callback.onResult("Upload cancelled");
}
}
private String readErrorResponse(HttpURLConnection connection) {
try {
InputStream errorStream = connection.getErrorStream();
if (errorStream != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException 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("Error closing input stream", e);
}
try {
if (outputStream != null) outputStream.close();
} catch (IOException e) {
AlertManager.error("Error closing output stream", e);
}
if (connection != null) {
connection.disconnect();
}
}
}
@@ -25,11 +25,24 @@ public class ImageRequestTask extends AsyncTask<String, Void, Bitmap> {
try { try {
URL url = new URL(src); URL url = new URL(src);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 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.setDoInput(true);
connection.connect(); connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input); int code = connection.getResponseCode();
} catch (FileNotFoundException e) { 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; return null;
} catch (IOException e){ } catch (IOException e){
AlertManager.error("Error downloading image " + src, 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 SelEVCodeKey = "selected_event_code";
public static final String YearNumKey = "year_num"; 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 MatchNumKey = "match_num";
public static final String AllyPosKey = "alliance_pos"; public static final String AllyPosKey = "alliance_pos";
@@ -28,6 +28,7 @@ public class SettingsManager {
public static final String BtUUIDKey = "bt_uuid"; public static final String BtUUIDKey = "bt_uuid";
public static final String FTPEnabled = "ftp_enabled"; public static final String FTPEnabled = "ftp_enabled";
public static final String FTPServer = "ftp_server"; public static final String FTPServer = "ftp_server";
public static final String FTPKey = "ftp_key";
public static final String FTPSendMetaFiles = "ftp_send_meta_files"; public static final String FTPSendMetaFiles = "ftp_send_meta_files";
public static final String EnableQuickAllianceChangeKey = "enable_quick_alliance_change"; public static final String EnableQuickAllianceChangeKey = "enable_quick_alliance_change";
@@ -46,14 +47,15 @@ public class SettingsManager {
hm.put(SelEVCodeKey, "unset"); hm.put(SelEVCodeKey, "unset");
hm.put(WifiModeKey, false); hm.put(WifiModeKey, false);
hm.put(YearNumKey, 2025); hm.put(YearNumKey, 2025);
hm.put(FieldImageKey, "2025"); hm.put(FieldImageKey, false);
hm.put(MatchNumKey, 0); hm.put(MatchNumKey, 0);
hm.put(AllyPosKey, "red-1"); hm.put(AllyPosKey, "red-1");
hm.put(DataModeKey, 0); hm.put(DataModeKey, 0);
hm.put(TeamsDataModeKey, 0); hm.put(TeamsDataModeKey, 0);
hm.put(BtUUIDKey, UUID.randomUUID().toString()); hm.put(BtUUIDKey, UUID.randomUUID().toString());
hm.put(FTPEnabled, false); hm.put(FTPEnabled, false);
hm.put(FTPServer, "0.0.0.0"); hm.put(FTPServer, "http://127.0.0.1:8080");
hm.put(FTPKey, "5uper_5ecure_k3y");
hm.put(FTPSendMetaFiles, false); hm.put(FTPSendMetaFiles, false);
hm.put(EnableQuickAllianceChangeKey, false); hm.put(EnableQuickAllianceChangeKey, false);
hm.put(CustomEventsKey, false); hm.put(CustomEventsKey, false);
@@ -75,7 +77,7 @@ public class SettingsManager {
getEditor().putBoolean(WifiModeKey, (boolean) defaults.get( WifiModeKey )).apply(); getEditor().putBoolean(WifiModeKey, (boolean) defaults.get( WifiModeKey )).apply();
getEditor() .putInt(YearNumKey, (int) defaults.get( YearNumKey )).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() .putInt(MatchNumKey, (int) defaults.get( MatchNumKey )).apply();
getEditor() .putString(AllyPosKey, (String) defaults.get( AllyPosKey )).apply(); getEditor() .putString(AllyPosKey, (String) defaults.get( AllyPosKey )).apply();
getEditor() .putInt(DataModeKey, (int) defaults.get( DataModeKey )).apply(); getEditor() .putInt(DataModeKey, (int) defaults.get( DataModeKey )).apply();
@@ -106,8 +108,8 @@ public class SettingsManager {
public static int getYearNum(){return prefs.getInt( YearNumKey, (int) defaults.get(YearNumKey));} 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 void setYearNum(int num){ getEditor().putInt( YearNumKey,num).apply();}
public static String getFieldImageIndex(){return prefs.getString( FieldImageKey, (String) defaults.get(FieldImageKey));} public static boolean getFieldImageFlipped(){return prefs.getBoolean(FieldImageKey, (boolean) defaults.get(FieldImageKey));}
public static void setFieldImageIndex(String str){ getEditor().putString( FieldImageKey,str).apply();} 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 int getMatchNum(){return prefs.getInt( MatchNumKey, (int) defaults.get(MatchNumKey));}
public static void setMatchNum(int num){ getEditor().putInt( MatchNumKey,num).apply();} public static void setMatchNum(int num){ getEditor().putInt( MatchNumKey,num).apply();}
@@ -131,6 +133,9 @@ public class SettingsManager {
public static String getFTPServer(){return prefs.getString( FTPServer, (String) defaults.get(FTPServer));} public static String getFTPServer(){return prefs.getString( FTPServer, (String) defaults.get(FTPServer));}
public static void setFTPServer(String str){ getEditor().putString( FTPServer,str).apply();} public static void setFTPServer(String str){ getEditor().putString( FTPServer,str).apply();}
public static String getFTPKey(){return prefs.getString( FTPKey, (String) defaults.get(FTPKey));}
public static void setFTPKey(String str){ getEditor().putString( FTPKey,str).apply();}
public static boolean getFTPSendMetaFiles(){return prefs.getBoolean(FTPSendMetaFiles, (boolean) defaults.get(FTPSendMetaFiles));} public static boolean getFTPSendMetaFiles(){return prefs.getBoolean(FTPSendMetaFiles, (boolean) defaults.get(FTPSendMetaFiles));}
public static void setFTPSendMetaFiles(boolean bool){getEditor().putBoolean(FTPSendMetaFiles,bool).apply();} public static void setFTPSendMetaFiles(boolean bool){getEditor().putBoolean(FTPSendMetaFiles,bool).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;
}
}
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M14,5v10l-9,-5 9,-5z"
android:fillColor="#000000"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M15,10l-9,5V5l9,5z"
android:fillColor="#000000"/>
</vector>
@@ -29,7 +29,31 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" 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 <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
+9 -11
View File
@@ -92,17 +92,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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 <TextView
android:id="@+id/text_match_alliance" android:id="@+id/text_match_alliance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -122,6 +111,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/event_button" /> 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> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -5,16 +5,28 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.ridgebotics.ridgescout.ui.views.MatchScoutingIndicator
android:id="@+id/bindicator"
android:layout_width="match_parent"
android:layout_height="60dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView <ScrollView
android:id="@+id/scrollView3"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<LinearLayout <LinearLayout
android:id="@+id/MatchScoutArea" android:id="@+id/MatchScoutArea"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="48dp"> android:paddingTop="48dp">
@@ -25,16 +37,15 @@
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_margin="5dp" android:layout_margin="5dp"
android:background="@drawable/border" android:background="@drawable/border"
android:padding="10dp" android:orientation="horizontal"
android:orientation="horizontal"> android:padding="10dp">
<TextView <TextView
android:id="@+id/scouting_notice_text" android:id="@+id/scouting_notice_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" android:text="Scouting Notice"
android:text="Scouting Notice"> android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"/>
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -46,75 +57,4 @@
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/file_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#60ff0000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/alliance_pos_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temp"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/matchnum"
app:layout_constraintStart_toEndOf="@+id/back_button"
app:layout_constraintTop_toBottomOf="@id/username" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temp"
android:textAlignment="center"
app:layout_constraintEnd_toStartOf="@+id/matchnum"
app:layout_constraintStart_toEndOf="@+id/back_button"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/matchnum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temp"
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/bar_team_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temp"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/next_button"
app:layout_constraintStart_toEndOf="@+id/matchnum"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -4,54 +4,27 @@
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout <com.ridgebotics.ridgescout.ui.views.PitScoutingIndicator
android:id="@+id/pit_file_indicator" android:id="@+id/pit_indicator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="60dp"
android:background="#60ff0000"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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>
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <LinearLayout
android:id="@+id/pitScoutArea" android:id="@+id/pitScoutArea"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="48dp"> android:paddingTop="60dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/scouting_notice_box" android:id="@+id/scouting_notice_box"
@@ -60,16 +33,15 @@
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_margin="5dp" android:layout_margin="5dp"
android:background="@drawable/border" android:background="@drawable/border"
android:padding="10dp" android:orientation="horizontal"
android:orientation="horizontal"> android:padding="10dp">
<TextView <TextView
android:id="@+id/scouting_notice_text" android:id="@+id/scouting_notice_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" android:text="Scouting Notice"
android:text="Scouting Notice"> android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"></TextView>
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
+56 -42
View File
@@ -10,7 +10,6 @@
android:id="@+id/scrollView2" android:id="@+id/scrollView2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:fillViewport="true" android:fillViewport="true"
android:visibility="visible" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@@ -18,55 +17,70 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TableLayout <LinearLayout
android:id="@+id/SettingsTable"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> 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> </ScrollView>
<Button <!-- <LinearLayout-->
android:id="@+id/fieldsButton" <!-- android:id="@+id/fieldsButtons"-->
android:layout_width="wrap_content" <!-- android:layout_width="match_parent"-->
android:layout_height="wrap_content" <!-- android:layout_height="wrap_content"-->
android:text="Fields" <!-- android:layout_marginTop="1dp"-->
android:textSize="34sp" <!-- android:gravity="center"-->
app:layout_constraintBottom_toBottomOf="@+id/scrollView2" <!-- android:orientation="horizontal"-->
app:layout_constraintEnd_toEndOf="parent" <!-- android:visibility="gone"-->
app:layout_constraintStart_toStartOf="parent" /> <!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintBottom_toTopOf="@+id/fieldsButton"-->
<!-- tools:visibility="visible">-->
<LinearLayout <!-- </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>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -14,8 +14,7 @@
android:id="@+id/teams" android:id="@+id/teams"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical" />
android:paddingTop="48dp" />
</ScrollView> </ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -7,77 +7,65 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <ImageView
android:id="@+id/scannerImage" android:id="@+id/scannerImage"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:rotation="90"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:scaleX="1"
android:scaleY="1"
android:translationY="-40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:context=".CodeScannerView" /> 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> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,31 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <ImageView
android:id="@+id/qrImage" android:id="@+id/qrImage"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -36,23 +15,48 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <LinearLayout
android:id="@+id/qrSpeedText" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_margin="3dp"
android:text="QR Speed" android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/qrSpeedSlider" android:background="@drawable/border"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/qrSizeText" <TextView
android:layout_width="wrap_content" android:id="@+id/qrSpeedText"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:layout_marginStart="8dp" android:layout_height="wrap_content"
android:text="QR Size" android:layout_marginStart="8dp"
app:layout_constraintBottom_toTopOf="@+id/qrSizeSlider" android:text="QR Speed"
app:layout_constraintStart_toStartOf="parent" /> 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 <TextView
android:id="@+id/qrIndexN" android:id="@+id/qrIndexN"
@@ -60,7 +64,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="0" android:text="0"
app:layout_constraintEnd_toStartOf="@+id/qrIndexSlash" 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 <TextView
android:id="@+id/qrIndexSlash" android:id="@+id/qrIndexSlash"
@@ -69,7 +74,8 @@
android:text="/" android:text="/"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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 <TextView
android:id="@+id/qrIndexD" android:id="@+id/qrIndexD"
@@ -77,6 +83,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="0" android:text="0"
app:layout_constraintStart_toEndOf="@+id/qrIndexSlash" 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> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -8,9 +8,12 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="3dp" android:layout_marginLeft="3dp"
android:orientation="horizontal" android:layout_marginTop="5dp"
android:layout_marginRight="3dp"
android:layout_marginBottom="3dp"
android:background="@drawable/border" android:background="@drawable/border"
android:orientation="horizontal"
tools:ignore="UselessParent"> tools:ignore="UselessParent">
<TextView <TextView
@@ -18,8 +21,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="10dp" android:layout_margin="10dp"
android:textSize="24sp" android:overlapAnchor="false"
android:overlapAnchor="false"/> android:text="▼ Options"
android:textSize="24sp" />
</LinearLayout> </LinearLayout>
@@ -28,9 +32,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="9dp" android:layout_marginStart="9dp"
android:layout_marginTop="-5dp" android:layout_marginTop="-6dp"
android:paddingLeft="3dp" android:paddingLeft="3dp"
android:paddingRight="3dp" android:paddingRight="3dp"
android:text="Test" android:text="Test"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"/> android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" />
</RelativeLayout> </RelativeLayout>
@@ -0,0 +1,103 @@
<?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/file_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/match_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" />
<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="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/match_indicator_next_button"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:contentDescription="Next"
android:src="@drawable/triangle_right"
android:text="Next"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/match_indicator_alliance_pos_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="58dp"
android:text="Temp"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintBottom_toBottomOf="parent"
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="58dp"
android:text="Temp"
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/match_indicator_matchnum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Temp"
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" />
<TextView
android:id="@+id/match_indicator_bar_team_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginTop="16dp"
android:text="Temp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintBottom_toBottomOf="@+id/match_indicator_matchnum"
app:layout_constraintStart_toEndOf="@+id/match_indicator_matchnum"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
@@ -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_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:text="0"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textSize="24sp" /> android:textSize="24sp" />
<Button <Button
+1 -1
View File
@@ -28,7 +28,7 @@
android:id="@+id/team_option_logo" android:id="@+id/team_option_logo"
android:layout_width="54dp" android:layout_width="54dp"
android:layout_height="54dp" android:layout_height="54dp"
android:layout_margin="3dp" android:layout_margin="2dp"
android:scaleType="fitXY" android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1" app:layout_constraintDimensionRatio="1:1"
+1 -1
View File
@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.8.0" agp = "8.13.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
espressoCore = "3.5.1" espressoCore = "3.5.1"
+1 -1
View File
@@ -1,6 +1,6 @@
#Sun Mar 24 10:48:55 MDT 2024 #Sun Mar 24 10:48:55 MDT 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
View File
+127
View File
@@ -0,0 +1,127 @@
from ast import mod
import os
import json
import hashlib
from datetime import datetime
from bottle import Bottle, run, get, put, static_file, response, request,HTTPResponse
from random import SystemRandom
from utils import *
app = Bottle()
file_metadata = {}
def save_metadata():
global file_metadata
write(METADATA_PATH4, json.dumps(file_metadata))
def load_metadata():
global file_metadata
data = read(METADATA_PATH4)
if data is not None:
file_metadata = json.loads(data)
api_key = None
cryptogen = SystemRandom()
def aquire_key():
global api_key
global cryptogen
if api_key is None:
try:
api_key = read(API_KEY_PATH).decode("utf-8").strip()
except:
ran = cryptogen.randrange(10**80)
api_key = "%064x" % ran
write(API_KEY_PATH, api_key)
@app.route('/')
def list_html():
global file_metadata
load_metadata()
content = '<html><body><table><tr>'
for heading in ['File', 'Size', 'Modified', 'Sha256']:
content += f'<th>{heading}</th>'
content += "</tr>"
print(file_metadata)
for filename in file_metadata.keys():
content += "<tr>"
content += f'<td><a href="/api/{filename}">{filename}</a></td>'
content += f'<td>{file_metadata[filename]["size"]}B</td>'
content += f'<td>{file_metadata[filename]["modified"]}</td>'
content += f'<td>{file_metadata[filename]["sha256"]}</td>'
content += "</tr>"
content += '</table></body></html>'
return content
@app.route('/api/metadata')
def metadata():
global file_metadata
load_metadata()
response.content_type = 'application/json'
return json.dumps(file_metadata)
@app.route('/api/<filename>', method='PUT')
def upload(filename):
global api_key
try:
sentkey = request.headers[API_KEY_HEADER]
if sentkey != api_key:
return HTTPResponse(status=403, body=f"Invalid Key")
except:
return HTTPResponse(status=403, body="You must specify an 'api_key' header")
global file_metadata
load_metadata()
data = request.body.read()
try:
modified = request.headers[MODIFIED_HEADER]
except:
modified = (datetime.now() - datetime(1970, 1, 1)).total_seconds()
sha256_hash = hashlib.sha256()
sha256_hash.update(data)
file_metadata[filename] = {
'size': len(data),
'modified': modified,
'sha256': sha256_hash.hexdigest()
}
save_metadata()
write(os.path.join(DATA_ROOT, filename), data)
# save_metadata()
# response.content_type = 'application/json'
# return json.dumps(file_metadata)
@app.route('/api/<filename>')
def download(filename):
data = read(os.path.join(DATA_ROOT, filename))
if data is not None:
response.content_type = 'application/octet-stream'
return data
else:
HTTPResponse(status=404, body="File not found")
if __name__ == '__main__':
mkdir(DATA_ROOT)
aquire_key()
app.run(host='0.0.0.0', port=8080)
+2
View File
@@ -0,0 +1,2 @@
bottle
random
+40
View File
@@ -0,0 +1,40 @@
import os
ROOT = os.path.dirname(__file__)
DATA_ROOT = os.path.join(os.path.dirname(__file__), 'server_data')
METADATA_PATH4 = os.path.join(ROOT, 'metadata.json')
API_KEY_PATH = os.path.join(ROOT, 'api_key.txt')
MODIFIED_HEADER = 'modified'
API_KEY_HEADER = 'api_key'
def mkdir(path):
if not os.path.exists(path):
os.makedirs(path)
def ls(path):
try:
return os.listdir(path)
except:
return []
def read(path):
if not os.path.exists(path):
return None
try:
with open(path, 'rb') as f:
return f.read()
except Exception as e:
print(f"Error reading file {path}: {e}")
return None
def write(path, data):
if not os.path.exists(path):
with open(path, mode='ab'): pass
if isinstance(data, str):
data = str.encode(data)
with open(path, 'wb') as f:
f.write(data)