28 Commits

Author SHA1 Message Date
Michael Mikovsky 4554db9dc7 Merge branch 'main' into increasing-verbosity 2025-10-08 11:57:48 -06:00
Michael Mikovsky b5b1100c2c Merge pull request #14 from Team4388/random-ui-fixes
Random UI fixes
2025-10-08 11:56:30 -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 ef761003c8 Increased the verbosity 2025-07-24 10:59:22 -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
74 changed files with 3258 additions and 1371 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 -3
View File
@@ -28,9 +28,8 @@
- Statbotics intgration - Statbotics intgration
- Scout error estimation using OPR-like calculation - Scout error estimation using OPR-like calculation
- - Would most likely require Statbotics - - Would most likely require Statbotics
https://www.thebluealliance.com/avatars
### 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")
@@ -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;
@@ -39,12 +41,11 @@ public class ScoutingArray {
continue; continue;
case CREATE: case CREATE:
new_values[i] = create_transfer((CreateTransferType) tv); new_values[i] = create_transfer((CreateTransferType) tv);
continue;
} }
} }
this.array = new_values; this.array = new_values;
version++; version++;
System.out.println("Updated to " + version); Log.i(getClass().toString(),"Updated to " + version);
} }
} }
@@ -70,12 +71,6 @@ public class ScoutingArray {
return get_data_type_by_UUID(tv.UUID); return get_data_type_by_UUID(tv.UUID);
} }
// private dataType rename_transfer(renameTransferType tv){
// dataType dt = get_data_type_by_name(tv.name);
// dt.name = tv.new_name;
// return dt;
// }
private RawDataType create_transfer(CreateTransferType tv){ private RawDataType create_transfer(CreateTransferType tv){
FieldType it = get_input_type_by_UUID(version+1, tv.UUID); FieldType it = get_input_type_by_UUID(version+1, tv.UUID);
switch (it.getValueType()){ switch (it.getValueType()){
@@ -32,12 +32,6 @@ public class ScoutingFile {
ByteBuilder bb = new ByteBuilder() ByteBuilder bb = new ByteBuilder()
.addString(filename); .addString(filename);
// byte[] data = Objects.requireNonNull(fileEditor.readFile(filename));
// for(int i = 0; i < data.length / 65535; i++){
// bb.addRaw(255, fileEditor.getByteBlock(data, i*65535, (i+1)*65535));
// }
bb.addRaw(255, Objects.requireNonNull(FileEditor.readFile(filename))); bb.addRaw(255, Objects.requireNonNull(FileEditor.readFile(filename)));
return bb.build(); return bb.build();
@@ -9,6 +9,7 @@ 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;
@@ -16,12 +17,13 @@ import java.util.stream.IntStream;
// 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 static final int typecode = 254; Unused, no idea what this is
public String eventCode; public String eventCode; //Current event code
public String name; public String name;
public ArrayList<frcMatch> matches; public ArrayList<frcMatch> matches;
public ArrayList<frcTeam> teams; public ArrayList<frcTeam> teams;
// Turns frcEvent into raw data
public byte[] encode() { public byte[] encode() {
try { try {
ByteBuilder bb = new ByteBuilder() ByteBuilder bb = new ByteBuilder()
@@ -47,6 +49,7 @@ public class frcEvent {
} }
} }
//Decodes the frcEvent
public static frcEvent decode(byte[] bytes) { public static frcEvent decode(byte[] bytes) {
try { try {
ArrayList<BuiltByteParser.parsedObject> objects = ArrayList<BuiltByteParser.parsedObject> objects =
@@ -75,6 +78,7 @@ public class frcEvent {
} }
} }
//Generates text
@NonNull @NonNull
public String toString() { public String toString() {
return ( return (
@@ -134,29 +138,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 +172,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));
@@ -207,6 +207,7 @@ public class CheckboxType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -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;
@@ -102,19 +103,17 @@ public class DropdownType extends FieldType {
// Dropdown view
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);
} }
@@ -123,7 +122,7 @@ public class DropdownType extends FieldType {
// Generates N amount of colors, all opposite colors
private static int[] generateEquidistantColors(int N) { private static int[] generateEquidistantColors(int N) {
int[] colors = new int[N]; int[] colors = new int[N];
float[] hsv = new float[3]; // Hue, Saturation, Value float[] hsv = new float[3]; // Hue, Saturation, Value
@@ -139,6 +138,7 @@ public class DropdownType extends FieldType {
return colors; return colors;
} }
// Turns the dropdown into a pie chart in the compiled view
public void add_compiled_view(LinearLayout parent, RawDataType[] data){ public void add_compiled_view(LinearLayout parent, RawDataType[] data){
PieChart chart = new PieChart(parent.getContext()); PieChart chart = new PieChart(parent.getContext());
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
@@ -172,7 +172,7 @@ public class DropdownType extends FieldType {
// Turns the dropdown into a line chart in the history view
public void add_history_view(LinearLayout parent, RawDataType[] data){ public void add_history_view(LinearLayout parent, RawDataType[] data){
LineChart chart = new LineChart(parent.getContext()); LineChart chart = new LineChart(parent.getContext());
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams( FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
@@ -239,6 +239,7 @@ public class DropdownType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -221,6 +221,7 @@ public class FieldposType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -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);
} }
@@ -316,6 +312,7 @@ public class NumberType extends FieldType {
parent.addView(chart); parent.addView(chart);
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -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));
}
}); });
} }
} }
@@ -228,6 +220,7 @@ public class TextType extends FieldType {
} }
//TODO
public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){ public void addDataToTable(TableLayout parent, Map<Integer, List<RawDataType>> data){
} }
@@ -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());
}
} }
@@ -19,6 +19,7 @@ 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.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.UUID; import java.util.UUID;
@@ -397,11 +398,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,26 +85,77 @@ 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 NumberSettingsItem(YearNumKey, "Year", 0, 9999));
manager.addItem(new HeaderSettingsItem("Connection"));
manager.addItem(new CheckboxSettingsItem(EnableQuickAllianceChangeKey, "Enable quick alliance swap", null));
manager.addItem(new DropdownSettingsItem(FieldImageKey, "Field Image", new String[]{ manager.addItem(new DropdownSettingsItem(FieldImageKey, "Field Image", new String[]{
"2025", "2025",
"2025 (Flipped)" "2025 (Flipped)"
})); }));
manager.addItem(new NumberSettingsItem(YearNumKey, "Year", 0, 9999));
manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list)); manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list));
int max = 0; int max = 0;
@@ -131,16 +177,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 +253,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 +394,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 +456,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 +475,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 +583,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 +692,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;
} }
@@ -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);
}
}
@@ -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);
@@ -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";
@@ -53,7 +54,8 @@ public class SettingsManager {
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);
@@ -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.
@@ -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)