36 Commits

Author SHA1 Message Date
Michael Mikovsky 99c3fa48dc Add block mean binarization 2025-12-06 11:45:23 -07:00
Daniel Carta e6073ded49 Update README.md 2025-11-04 12:03:29 -07:00
Daniel Carta 7503cefa09 Update Screenshots
Changed old screenshots to new ones
2025-11-04 12:01:58 -07:00
Michael Mikovsky 3b4737e6bc Change version 2025-10-08 20:37:30 -06:00
Michael Mikovsky 76d28e46cd Update version to 2.1 2025-10-08 20:27:05 -06:00
Michael Mikovsky 73308f2acc Fix TBA Event coloring 2025-10-08 20:23:11 -06:00
Michael Mikovsky c18a93cb1a Change dashboard if there are no matches 2025-10-08 20:06:25 -06:00
Michael Mikovsky 3ca601f080 Merge pull request #15 from Team4388/increasing-verbosity
Add more comments
2025-10-08 11:57:56 -06:00
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
82 changed files with 3390 additions and 1384 deletions
Vendored
BIN
View File
Binary file not shown.
+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/
+5 -7
View File
@@ -11,10 +11,10 @@
#### Here is an overview of the main features currently included in the app: #### Here is an overview of the main features currently included in the app:
- This project is written for Android! No need for some kind of janky laptop charging setup. - This project is written for Android! No need for some kind of janky laptop charging setup.
- Similar to ScoutingPASS, there are many diffrent types of fields that can be used to collect data. - Similar to ScoutingPASS, many different types of fields can be used to collect data.
- The app is designed to handle updates to the fields on the fly, without loosing any data! - The app is designed to handle updates to the fields on the fly, without losing any data!
- Unlike other scouting solutions, scouters can disable any field they did not measure, and disabled fields will not be included in any calculations. - Unlike other scouting solutions, scouters can disable any field they did not measure, and disabled fields will not be included in any calculations.
- Dynamic displays based off of the diffrent fields. - Dynamic displays based on the different fields.
- Data transfer including 2D codes, Bluetooth, and File Bundle. - Data transfer including 2D codes, Bluetooth, and File Bundle.
- Exporting using CSV. - Exporting using CSV.
- Deployment on F-Droid - Deployment on F-Droid
@@ -22,15 +22,13 @@
#### Things that are yet to be implemented: #### Things that are yet to be implemented:
- A page that lets users cross-compare scouting data between teams. (Compare) - A page that lets users cross-compare scouting data between teams. (Compare)
- A page that lets scouters more easily make reports to the drive team before a match starts (Report)
#### Things that may or may not be implemented: #### Things that may or may not be implemented:
- Statbotics intgration - Statbotics integration
- 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:
+9 -9
View File
@@ -16,7 +16,7 @@ android {
dependenciesInfo { dependenciesInfo {
// Disables dependency metadata when building APKs. // Disables dependency metadata when building APKs.
includeInApk = false includeInApk = false
// Disables dependency metadata when building Android App Bundles. // Disables dependency metadata when building Android App Bundles.5
includeInBundle = false includeInBundle = false
} }
@@ -25,8 +25,8 @@ android {
applicationId = "com.ridgebotics.ridgescout" applicationId = "com.ridgebotics.ridgescout"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 34
versionCode = 11 // **IMPORTANT** Increment this before releasing on github versionCode = 12 // **IMPORTANT** Increment this before releasing on github
versionName = "1.4"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>) versionName = "2.0"// **IMPORTANT** Change this before releasing on github (<Year num since 2024>.<Update Version>)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -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,11 +60,11 @@ 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];
@@ -70,8 +72,7 @@ public class ScoutingDataWriter {
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);
@@ -82,17 +83,17 @@ public class ScoutingDataWriter {
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;
} }
} }
@@ -109,10 +110,7 @@ public class ScoutingDataWriter {
return psda; return psda;
} catch (BuiltByteParser.byteParsingExeption e){ // }
AlertManager.error(e);
return null;
}
} }
// 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);
int maxMatch = Integer.MIN_VALUE;
for(int i = 0; i < teamMatches.length; i++){
int maxMatch = -1;
for(int a = 0; a < 6; a++){ for(int a = 0; a < 6; a++){
int teamNum; int teamNum;
if(a < 3) if(a < 3)
teamNum = teamMatches[i].redAlliance[a]; teamNum = teamMatch.redAlliance[a];
else else
teamNum = teamMatches[i].blueAlliance[a-3]; teamNum = teamMatch.blueAlliance[a-3];
if(teamNum == ourTeamNum) if(teamNum == ourTeamNum)
continue; continue;
int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex); int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatch.matchIndex);
if(maxMatch < matchNum) if(maxMatch < matchNum)
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
public void onFinish(float sentiment) {
positive_mean += sentiment; positive_mean += sentiment;
count++; 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,38 @@ public class ScoutingFragment extends Fragment {
}); });
} }
private void updateDashboard() {
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
if(event.matches.size() == 0) {
binding.textMatchAlliance.setText("No Matches!");
} else {
int teamNum = SettingsManager.getTeamNum();
int curMatchNum = SettingsManager.getMatchNum();
binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos());
int nextMatch;
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.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,18 +221,12 @@ 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, "#");
@@ -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();
rq.onResult(s -> {
try {
JSONArray jsonArray = new JSONArray(s);
JSONObject jsonObject = jsonArray.getJSONObject(0);
String base64 = jsonObject.getJSONObject("details").getString("base64Image");
byte[] decodedData = Base64.decode(base64, Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(decodedData, 0, decodedData.length);
// System.out.println(base64);
teamObj.bitmap = bitmap; teamObj.bitmap = bitmap;
teamObj.teamColor = frcTeam.findPrimaryColor(bitmap); teamObj.teamColor = frcTeam.findPrimaryColor(bitmap);
teams.add(teamObj);
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");
@@ -178,12 +165,12 @@ public class TBASelectorFragment extends Fragment {
try { try {
Date startDate = format.parse(j.getString("start_date")); Date startDate = format.parse(j.getString("start_date"));
Date endDate = format.parse(j.getString("end_date")); Date endDate = format.parse(j.getString("end_date"));
if(currentTime.after(endDate)){ if(currentTime.after(startDate) && currentTime.before(endDate)) {
row.setColor(tba_current);
} else if(currentTime.after(endDate)){
row.setColor(tba_previous); row.setColor(tba_previous);
}else if(currentTime.before(startDate)){ }else if(currentTime.before(startDate)){
row.setColor(tba_next); row.setColor(tba_next);
}else if(currentTime.after(startDate) && currentTime.before(endDate)){
row.setColor(tba_current);
} }
} catch (Exception e) { } catch (Exception e) {
AlertManager.error("Failed finding start and end dates!", e); AlertManager.error("Failed finding start and end dates!", e);
@@ -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;
int randID = new Random().nextInt(255); AlertManager.stopLoading();
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; 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();
Runnable runnable = new Runnable() {
@Override
public void run() {
if(shouldstop){
return;
}
try{
updateQr(); updateQr();
qrLoop();
} }
}.start(); 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);
}
}
};
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
@@ -91,6 +87,10 @@ public class CodeScannerView extends Fragment {
); );
} }
} }
private static final int BLOCK_SIZE = 32; // Size of each block in pixels
private Bitmap toGreyscale(Image image){ private Bitmap toGreyscale(Image image){
// Turns out the "Y" In YUV is the Luminance of the pixel. // Turns out the "Y" In YUV is the Luminance of the pixel.
// Makes converting to greyscale 1000x easier // Makes converting to greyscale 1000x easier
@@ -98,24 +98,137 @@ public class CodeScannerView extends Fragment {
final int width = image.getWidth(); final int width = image.getWidth();
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]; int L = yBuffer.get(i) & 0xff;
pixels[y * width + x] = 0xff000000 | (L << 16) | (L << 8) | L; pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L;
}
} }
threshold(pixels, width, height);
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;
} }
/**
* Performs mean block binarization.
* * Note: The function name 'toGreyscale' is kept per your request,
* but this method actually performs binarization (Black/White).
*
* @param image The array of pixels (ARGB format, standard in Android)
* @param width The width of the image
* @param height The height of the image
*/
private void threshold(int[] image, int width, int height) {
// 1. Setup Block Grid Dimensions
// We use Math.ceil to ensure partial blocks at the edges are counted
int gridCols = (int) Math.ceil((double) width / BLOCK_SIZE);
int gridRows = (int) Math.ceil((double) height / BLOCK_SIZE);
// Arrays to store statistics for each block
int[] blockSums = new int[gridCols * gridRows];
int[] blockCounts = new int[gridCols * gridRows];
int[] blockMeans = new int[gridCols * gridRows];
// --- PASS 1: Compute Block Statistics (Mean Intensity) ---
for (int y = 0; y < height; y++) {
int blockY = y / BLOCK_SIZE;
for (int x = 0; x < width; x++) {
int blockX = x / BLOCK_SIZE;
int blockIndex = blockY * gridCols + blockX;
// Extract average grayscale value from ARGB pixel
int pixel = image[y * width + x];
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
// Simple average (matches BoofCV's typical approach for generic buffers)
int gray = (r + g + b) / 3;
blockSums[blockIndex] += gray;
blockCounts[blockIndex]++;
}
}
// Calculate the mean for every block
for (int i = 0; i < blockSums.length; i++) {
if (blockCounts[i] > 0) {
blockMeans[i] = blockSums[i] / blockCounts[i];
}
}
// --- PASS 2: Apply Threshold using Local 3x3 Block Region ---
for (int blockY = 0; blockY < gridRows; blockY++) {
for (int blockX = 0; blockX < gridCols; blockX++) {
// Calculate the threshold for this specific block
// by averaging the means of the surrounding 3x3 blocks.
// This corresponds to BoofCV's `thresholdFromLocalBlocks` logic.
long localSum = 0;
int localCount = 0;
int startGridY = Math.max(0, blockY - 1);
int endGridY = Math.min(gridRows - 1, blockY + 1);
int startGridX = Math.max(0, blockX - 1);
int endGridX = Math.min(gridCols - 1, blockX + 1);
for (int ny = startGridY; ny <= endGridY; ny++) {
for (int nx = startGridX; nx <= endGridX; nx++) {
localSum += blockMeans[ny * gridCols + nx];
localCount++;
}
}
int threshold = (localCount > 0) ? (int) (localSum / localCount) : 127;
// Apply this threshold to all pixels within the current block
int startPixelX = blockX * BLOCK_SIZE;
int startPixelY = blockY * BLOCK_SIZE;
// Handle image boundary (if image size isn't perfectly divisible by block size)
int endPixelX = Math.min(startPixelX + BLOCK_SIZE, width);
int endPixelY = Math.min(startPixelY + BLOCK_SIZE, height);
for (int y = startPixelY; y < endPixelY; y++) {
for (int x = startPixelX; x < endPixelX; x++) {
int index = y * width + x;
// Recalculate gray to compare against threshold
int pixel = image[index];
int r = (pixel >> 16) & 0xFF;
int g = (pixel >> 8) & 0xFF;
int b = pixel & 0xFF;
int gray = (r + g + b) / 3;
// Binarize: Black if <= threshold, White if > threshold
if (gray <= threshold) {
image[index] = 0xFF000000; // Black (Alpha 255)
} else {
image[index] = 0xFFFFFFFF; // White (Alpha 255)
}
}
}
}
}
}
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(
@@ -126,10 +239,7 @@ public class CodeScannerView extends Fragment {
data.substring(4) data.substring(4)
); );
} }
return null;
}); });
async.execute();
// return contents; // return contents;
@@ -231,26 +341,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 +376,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 +403,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 +422,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 +463,8 @@ public class CodeScannerView extends Fragment {
}catch (Exception e){ }catch (Exception e){
AlertManager.error(e); AlertManager.error(e);
} }
}
prevQrIndex = qrIndex;
}
return null;
}
}
} }
@@ -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
@@ -67,9 +67,9 @@ public class Colors {
public static final int fileselector_unselected_color = 0x50006600; public static final int fileselector_unselected_color = 0x50006600;
// TBA // TBA
public static final int tba_previous = 0x30FF0000; public static final int tba_previous = Color.argb(30, 64,64,64);
public static final int tba_current = 0x50ff0000; public static final int tba_current = 0x7f00ff00;
public static final int tba_next = 0x30FFFF00; public static final int tba_next = Color.argb(30, 192,192,192);
public static final int tba_red = 0x50ff0000; public static final int tba_red = 0x50ff0000;
public static final int tba_blue = 0x500000ff; public static final int tba_blue = 0x500000ff;
public static final int tba_toggle_background = 0x30000000; public static final int tba_toggle_background = 0x30000000;
@@ -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,36 +59,38 @@ 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"); } catch (Exception e){
return; AlertManager.error("Error saving rescouting list", e);
}
} }
ByteBuilder bb = new ByteBuilder();
bb.addStringArray(rescout_list.toArray(new String[0]));
FileEditor.writeFile(evcode + ".rescout", bb.build());
} catch (Exception e){
AlertManager.error("Error saving scout fields", e);
}
}
public static String scoutNotice = ""; public static String scoutNotice = "";
@@ -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,10 +25,23 @@ 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();
int code = connection.getResponseCode();
switch (code) {
case 200:
InputStream input = connection.getInputStream(); InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input); 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){ } catch (FileNotFoundException e){
return null; return null;
} catch (IOException e){ } catch (IOException 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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/pit_bar_team_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4388"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_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/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,14 +15,30 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:orientation="vertical"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView <TextView
android:id="@+id/qrSpeedText" android:id="@+id/qrSpeedText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:text="QR Speed" android:text="QR Speed"
app:layout_constraintBottom_toTopOf="@+id/qrSpeedSlider" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintBottom_toTopOf="@+id/qrSpeedSlider" />
<SeekBar
android:id="@+id/qrSpeedSlider"
android:layout_width="match_parent"
android:layout_height="48dp" />
<TextView <TextView
android:id="@+id/qrSizeText" android:id="@+id/qrSizeText"
@@ -51,8 +46,17 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:text="QR Size" android:text="QR Size"
app:layout_constraintBottom_toTopOf="@+id/qrSizeSlider" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
app:layout_constraintStart_toStartOf="parent" />
<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,7 +32,7 @@
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"
@@ -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
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 454 KiB

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)