diff --git a/README.md b/README.md index 0c6383c..bc073c5 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,8 @@ - Statbotics intgration - Scout error estimation using OPR-like calculation - - Would most likely require Statbotics - +https://www.thebluealliance.com/avatars ### Screenshots |Match scouting interface|Field editor|Teams data viewer| |-|-|-| -|![Screenshot1](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/1.png?raw=true)|![Screenshot2](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/2.png?raw=true)|![Screenshot3](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/3.png?raw=true)| - +|![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)| \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bb64a93..fbb3917 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,8 +44,8 @@ android { buildFeatures { viewBinding = true } - aaptOptions { - noCompress("tflite"); + androidResources { + noCompress += listOf("tflite") } } @@ -66,10 +66,10 @@ dependencies { androidTestImplementation(libs.espresso.core) - var camerax_version = "1.3.2" - implementation("androidx.camera:camera-core:1.3.2") - implementation("androidx.camera:camera-camera2:1.3.2") - implementation("androidx.camera:camera-lifecycle:1.3.2") + var camerax_version = "1.4.2" + implementation("androidx.camera:camera-core:${camerax_version}") + implementation("androidx.camera:camera-camera2:${camerax_version}") + implementation("androidx.camera:camera-lifecycle:${camerax_version}") implementation("androidx.camera:camera-view:${camerax_version}") implementation("com.journeyapps:zxing-android-embedded:4.3.0") diff --git a/app/src/main/java/com/ridgebotics/ridgescout/scoutingData/ScoutingDataWriter.java b/app/src/main/java/com/ridgebotics/ridgescout/scoutingData/ScoutingDataWriter.java index 2ae854e..029352a 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/scoutingData/ScoutingDataWriter.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/scoutingData/ScoutingDataWriter.java @@ -60,61 +60,57 @@ public class ScoutingDataWriter { public ScoutingArray data; } - public static ParsedScoutingDataResult load(String filename, FieldType[][] values , TransferType[][] transferValues){ + public static ParsedScoutingDataResult load(String filename, FieldType[][] values , TransferType[][] transferValues) throws BuiltByteParser.byteParsingExeption{ byte[] bytes = FileEditor.readFile(filename); BuiltByteParser bbp = new BuiltByteParser(bytes); - try { - ArrayList objects = bbp.parse(); - RawDataType[] rawDataTypes = new RawDataType[objects.size()-2]; +// try { + ArrayList objects = bbp.parse(); + RawDataType[] rawDataTypes = new RawDataType[objects.size()-2]; - int version = ((int)objects.get(0).get()); + int version = ((int)objects.get(0).get()); - if(values.length <= version) { + if(values.length <= version) { // AlertManager.addSimpleError("Error loading " + filename); - AlertManager.error(new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!")); - return null; - } + throw new BuiltByteParser.byteParsingExeption("Field version (" +version + ") is too recent as compared to latest version (" + (values.length-1) + ")!"); + } // System.out.println(version); - String username = (String) objects.get(1).get(); + String username = (String) objects.get(1).get(); - for(int i = 0; i < values[version].length; i++){ - switch (objects.get(i+2).getType()){ - case 1: // Int - rawDataTypes[i] = IntType.newNull(values[version][i].UUID); - rawDataTypes[i].forceSetValue(objects.get(i+2).get()); - Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); - break; - case 2: // String - rawDataTypes[i] = StringType.newNull(values[version][i].UUID); - rawDataTypes[i].forceSetValue(objects.get(i+2).get()); - Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); - break; - case 3: // Int array - rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID); - rawDataTypes[i].forceSetValue(objects.get(i+2).get()); - Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")"); - break; - } + for(int i = 0; i < values[version].length; i++){ + switch (objects.get(i+2).getType()){ + case 1: // Int + rawDataTypes[i] = IntType.newNull(values[version][i].UUID); + rawDataTypes[i].forceSetValue(objects.get(i+2).get()); + Log.i(ParsedScoutingDataResult.class.toString(),"Loaded INT: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); + break; + case 2: // String + rawDataTypes[i] = StringType.newNull(values[version][i].UUID); + rawDataTypes[i].forceSetValue(objects.get(i+2).get()); + Log.i(ParsedScoutingDataResult.class.toString(),"Loaded STR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ rawDataTypes[i].get() +")"); + break; + case 3: // Int array + rawDataTypes[i] = IntArrType.newNull(values[version][i].UUID); + rawDataTypes[i].forceSetValue(objects.get(i+2).get()); + Log.i(ParsedScoutingDataResult.class.toString(),"Loaded intARR: " + values[version][i].name + " (" + values[version][i].UUID + ") " + ", ("+ Arrays.toString((int[]) rawDataTypes[i].get()) +")"); + break; } - - ScoutingArray msa = new ScoutingArray(version, rawDataTypes, values, transferValues); - msa.update(); - - ParsedScoutingDataResult psda = new ParsedScoutingDataResult(); - - psda.filename = filename; - psda.username = username; - psda.version = version; - psda.data = msa; - - return psda; - - } catch (BuiltByteParser.byteParsingExeption e){ - AlertManager.error(e); - return null; } + + ScoutingArray msa = new ScoutingArray(version, rawDataTypes, values, transferValues); + msa.update(); + + ParsedScoutingDataResult psda = new ParsedScoutingDataResult(); + + psda.filename = filename; + psda.username = username; + psda.version = version; + psda.data = msa; + + return psda; + +// } } // A function that takes in a list of names seperated by commas, and adds a name if it is not included diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/ColabArray.java b/app/src/main/java/com/ridgebotics/ridgescout/types/ColabArray.java new file mode 100644 index 0000000..528c74e --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/ColabArray.java @@ -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 changelog = new ArrayList<>(); + + private void addChange(Diff change) { + this.changelog.add(change); + } + + private List 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 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 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 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 get() { + List 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(); + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java b/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java index 71a7470..476b4a1 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/frcEvent.java @@ -9,6 +9,7 @@ import com.ridgebotics.ridgescout.utility.ByteBuilder; import com.ridgebotics.ridgescout.utility.SettingsManager; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; @@ -137,27 +138,29 @@ public class frcEvent { return null; } + // Returns the soonest match that there will be all the possible upcoming data on other teams - public void getReportMatches(int ourTeamNum){ - frcMatch[] teamMatches = event.getTeamMatches(ourTeamNum); + public int getMostInformedBy(int ourTeamNum, int curMatch){ + frcMatch teamMatch = getNextTeamMatch(ourTeamNum, curMatch); - for(int i = 0; i < teamMatches.length; i++){ - int maxMatch = -1; - for(int a = 0; a < 6; a++){ - int teamNum; - if(a < 3) - teamNum = teamMatches[i].redAlliance[a]; - else - teamNum = teamMatches[i].blueAlliance[a-3]; + int maxMatch = Integer.MIN_VALUE; - if(teamNum == ourTeamNum) - continue; + for(int a = 0; a < 6; a++){ + int teamNum; + if(a < 3) + teamNum = teamMatch.redAlliance[a]; + else + teamNum = teamMatch.blueAlliance[a-3]; - int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatches[i].matchIndex); - if(maxMatch < matchNum) - maxMatch = matchNum; - } + if(teamNum == ourTeamNum) + continue; + + int matchNum = event.getMostRecentTeamMatch(teamNum, teamMatch.matchIndex); + if(maxMatch < matchNum) + maxMatch = matchNum; } + + return maxMatch; } public frcTeam getTeamByNum(int teamNum){ @@ -169,6 +172,26 @@ public class frcEvent { return null; } + public List 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 list = new ArrayList<>(); + + for(int i = 0; i < teams.size(); i++){ + frcTeam team = getTeamByNum(teamNums[i]); + assert team != null; + list.add(team); + } + + return list; + } + public boolean getIsBlueAlliance(int teamNum, int matchNum){ return getIsBlueAlliance(teamNum, matches.get(matchNum)); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/input/DropdownType.java b/app/src/main/java/com/ridgebotics/ridgescout/types/input/DropdownType.java index 11df904..5358437 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/input/DropdownType.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/input/DropdownType.java @@ -29,6 +29,7 @@ import com.github.mikephil.charting.data.LineDataSet; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; import com.github.mikephil.charting.data.PieEntry; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Arrays; @@ -105,16 +106,14 @@ public class DropdownType extends FieldType { // Dropdown view public void add_individual_view(LinearLayout parent, RawDataType data){ if(data.isNull()) return; - TextView tv = new TextView(parent.getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setPadding(20,20,20,20); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(text_options[(int) data.get()]); - tv.setTextSize(18); - parent.addView(tv); + + parent.addView( + new TextViewBuilder(parent.getContext(), text_options[(int) data.get()]) + .layout_match_wrap() + .padding(20) + .size(18) + .align_center() + .build()); } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/input/NumberType.java b/app/src/main/java/com/ridgebotics/ridgescout/types/input/NumberType.java index 70ec81b..194a07d 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/input/NumberType.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/input/NumberType.java @@ -29,6 +29,7 @@ import com.ridgebotics.ridgescout.types.data.RawDataType; import com.ridgebotics.ridgescout.types.data.IntType; import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.ByteBuilder; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.List; @@ -118,16 +119,11 @@ public class NumberType extends FieldType { public void add_individual_view(LinearLayout parent, RawDataType data){ if(data.isNull()) return; - - TextView tv = new TextView(parent.getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(String.valueOf((int) data.get())); - tv.setTextSize(24); - parent.addView(tv); + parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get())) + .layout_match_wrap() + .align_center() + .size(24) + .build()); } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/input/TallyType.java b/app/src/main/java/com/ridgebotics/ridgescout/types/input/TallyType.java index c504e65..3231afa 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/input/TallyType.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/input/TallyType.java @@ -29,6 +29,7 @@ import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Collections; @@ -103,16 +104,11 @@ public class TallyType extends FieldType { public void add_individual_view(LinearLayout parent, RawDataType data){ if(data.isNull()) return; - - TextView tv = new TextView(parent.getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(String.valueOf((int) data.get())); - tv.setTextSize(24); - parent.addView(tv); + parent.addView(new TextViewBuilder(parent.getContext(), String.valueOf((int) data.get())) + .layout_match_wrap() + .align_center() + .size(24) + .build()); } @@ -344,16 +340,12 @@ public class TallyType extends FieldType { row = new TableRow(parent.getContext()); CandlestickView view = views.get(i); - TextView teamNum = new TextView(parent.getContext()); - TableRow.LayoutParams params = new TableRow.LayoutParams(); - params.gravity = Gravity.CENTER; - teamNum.setLayoutParams(params); - teamNum.setPadding(10,10,10,10); - teamNum.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6); - teamNum.setText(String.valueOf(view.teamNum)); + row.addView(new TextViewBuilder(parent.getContext(), String.valueOf(view.teamNum)) + .align_center() + .padding(10) + .h6() + .build()); - - row.addView(teamNum); row.addView(view); parent.addView(row); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/types/input/TextType.java b/app/src/main/java/com/ridgebotics/ridgescout/types/input/TextType.java index 2566e8b..80e85a8 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/types/input/TextType.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/types/input/TextType.java @@ -28,6 +28,7 @@ import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.LineData; import com.github.mikephil.charting.data.LineDataSet; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.List; @@ -112,15 +113,11 @@ public class TextType extends FieldType { public void add_individual_view(LinearLayout parent, RawDataType data){ if(data.isNull()) return; - TextView tv = new TextView(parent.getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText((String) data.get()); - tv.setTextSize(18); - parent.addView(tv); + parent.addView(new TextViewBuilder(parent.getContext(), (String) data.get()) + .layout_match_wrap() + .align_center() + .size(18) + .build()); } @@ -144,25 +141,20 @@ public class TextType extends FieldType { positive_mean = 0; count = 0; - positive_text = new TextView(parent.getContext()); - positive_text.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - positive_text.setGravity(Gravity.CENTER_HORIZONTAL); - positive_text.setTextSize(20); + positive_text = new TextViewBuilder(parent.getContext()) + .align_center() + .size(20) + .build(); + parent.addView(positive_text); for (int i = 0; i < data.length; i++){ if (!data[i].isNull()) { - SentimentAnalysis.analyse((String) data[i].get(), new SentimentAnalysis.resultCallback() { - @Override - public void onFinish(float sentiment) { - positive_mean += sentiment; - count++; + SentimentAnalysis.analyse((String) data[i].get(), sentiment -> { + positive_mean += sentiment; + count++; - positive_text.setText("Sentiment: " + (positive_mean / count)); - } + positive_text.setText("Sentiment: " + (positive_mean / count)); }); } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldDataFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldDataFragment.java index a646cb0..514285d 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldDataFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldDataFragment.java @@ -58,7 +58,7 @@ public class FieldDataFragment extends Fragment { for (int teamIndex = 0; teamIndex < event.teams.size(); teamIndex++) { int teamNum = event.teams.get(teamIndex).teamNumber; List filenames = new ArrayList<>(List.of(FileEditor.getMatchesByTeamNum(evcode, event.teams.get(teamIndex).teamNumber))); - filenames.removeAll(rescout_list); + filenames.removeAll(rescout_list.get()); ArrayList teamData = new ArrayList<>(); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/TeamsFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/TeamsFragment.java index 76a5a64..5ff9277 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/TeamsFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/TeamsFragment.java @@ -10,6 +10,8 @@ import static com.ridgebotics.ridgescout.utility.DataManager.pit_latest_values; import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues; import static com.ridgebotics.ridgescout.utility.DataManager.pit_values; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; @@ -23,6 +25,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.ridgebotics.ridgescout.utility.AlertManager; +import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.databinding.FragmentDataTeamsBinding; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; @@ -30,6 +33,7 @@ import com.ridgebotics.ridgescout.types.data.RawDataType; import com.ridgebotics.ridgescout.types.frcTeam; import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.FileEditor; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.List; @@ -66,38 +70,37 @@ public class TeamsFragment extends Fragment { loadTeam(index); }); + binding.tbaButton.setOnClickListener(v -> openWebPage( + "https://www.thebluealliance.com/team/"+team.teamNumber+"/"+SettingsManager.getYearNum() + )); + + binding.statboticsButton.setOnClickListener(v -> openWebPage( + "https://www.statbotics.io/team/"+team.teamNumber+"/"+SettingsManager.getYearNum() + )); + loadTeam(SettingsManager.getTeamsDataMode()); return binding.getRoot(); } + public void openWebPage(String url) { + Uri webpage = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, webpage); +// if (intent.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(intent); +// } + } + public void loadTeam(int mode) { - -// LinearLayout ll = new LinearLayout(getContext()); -// ll.setLayoutParams(new LinearLayout.LayoutParams( -// ViewGroup.LayoutParams.MATCH_PARENT, -// ViewGroup.LayoutParams.WRAP_CONTENT -// )); -// ll.setOrientation(LinearLayout.VERTICAL); -// binding.teamsArea.addView(ll); - binding.dataTeamCard.fromTeam(team); -// tv = new TextView(getContext()); -// tv.setLayoutParams(new FrameLayout.LayoutParams( -// ViewGroup.LayoutParams.MATCH_PARENT, -// ViewGroup.LayoutParams.WRAP_CONTENT -// )); -// tv.setGravity(Gravity.CENTER_HORIZONTAL); -// tv.setText(team.getDescription()); -// tv.setTextSize(16); -// ll.addView(tv); + try {add_pit_data(team);}catch(Exception e){AlertManager.error(e);} try {add_match_data(team, mode);}catch(Exception e){AlertManager.error(e);} } - public void add_pit_data(frcTeam team){ + public void add_pit_data(frcTeam team) throws BuiltByteParser.byteParsingExeption { binding.pitArea.removeAllViews(); final String filename = evcode+"-"+team.teamNumber+".pitscoutdata"; @@ -117,49 +120,34 @@ public class TeamsFragment extends Fragment { // ll.addView(new MaterialDivider(getContext())); if(!FileEditor.fileExist(filename)){ - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("No pit data has been collected!"); - tv.setTextSize(23); - binding.pitArea.addView(tv); + binding.pitArea.addView(new TextViewBuilder(getContext(), "No pit data has been collected!") + .layout_match_wrap().align_center().size(23).build()); return; } ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(filename, pit_values, pit_transferValues); - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setPadding(0, 20, 0, 5); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Pit scouting by " + psda.username); - tv.setTextSize(30); - binding.pitArea.addView(tv); + + binding.pitArea.addView(new TextViewBuilder(getContext(), "Pit scouting by " + psda.username) + .layout_match_wrap() + .padding(0, 20, 0, 5) + .align_center() + .size(30) + .build() + ); for (int a = 0; a < psda.data.array.length; a++) { - tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(pit_latest_values[a].name); - tv.setTextSize(25); + TextViewBuilder tvb = new TextViewBuilder(getContext(), pit_latest_values[a].name) + .align_center() + .layout_match_wrap() + .size(25); if(psda.data.array[a].isNull()){ - tv.setBackgroundColor(toggletitle_unselected); - tv.setTextColor(toggletitle_black_background); + tvb.tv.setBackgroundColor(toggletitle_unselected); + tvb.tv.setTextColor(toggletitle_black_background); } - - - binding.pitArea.addView(tv); + binding.pitArea.addView(tvb.build()); pit_latest_values[a].add_individual_view(binding.pitArea, psda.data.array[a]); @@ -175,15 +163,11 @@ public class TeamsFragment extends Fragment { String[] files = FileEditor.getMatchesByTeamNum(evcode, team.teamNumber); if(files.length == 0){ - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("No match data has been collected!"); - tv.setTextSize(23); - binding.matchArea.addView(tv); + binding.matchArea.addView(new TextViewBuilder(getContext(), "No match data has been collected!") + .layout_match_wrap() + .align_center() + .size(23) + .build()); return; } @@ -236,33 +220,28 @@ public class TeamsFragment extends Fragment { ScoutingDataWriter.ParsedScoutingDataResult psda = ScoutingDataWriter.load(files[matchIndex], match_values, match_transferValues); - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setPadding(0, 40, 0, 5); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username); - tv.setTextSize(30); - binding.matchArea.addView(tv); + + binding.matchArea.addView( + new TextViewBuilder(getContext(), "M" + (match_num) + " " + split[2] + "-" + split[3] + " by " + psda.username) + .align_center() + .size(30) + .padding(0,0,40,5) + .build() + + ); + for (int i = 0; i < psda.data.array.length; i++) { - tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(match_latest_values[i].name); - tv.setTextSize(25); + TextViewBuilder tv = new TextViewBuilder(getContext(), match_latest_values[i].name) + .align_center() + .size(25); if (psda.data.array[i].isNull()) { - tv.setBackgroundColor(toggletitle_unselected); - tv.setTextColor(toggletitle_black_background); + tv.tv.setBackgroundColor(toggletitle_unselected); + tv.tv.setTextColor(toggletitle_black_background); } - binding.matchArea.addView(tv); + binding.matchArea.addView(tv.build()); if(psda.data.array[i] != null) @@ -294,16 +273,14 @@ public class TeamsFragment extends Fragment { } for(int i = 0; i < match_latest_values.length; i++){ - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setPadding(0, 20, 0, 5); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(match_latest_values[i].name); - tv.setTextSize(30); - binding.matchArea.addView(tv); + + binding.matchArea.addView( + new TextViewBuilder(getContext(), match_latest_values[i].name) + .align_center() + .padding(0, 0, 20, 5) + .size(30) + .build() + ); if(data[i] != null) match_latest_values[i].add_compiled_view(binding.matchArea, data[i]); @@ -329,19 +306,18 @@ public class TeamsFragment extends Fragment { } for(int i = 0; i < match_latest_values.length; i++){ - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setPadding(0, 20, 0, 5); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(match_latest_values[i].name); - tv.setTextSize(30); - binding.matchArea.addView(tv); + + binding.matchArea.addView( + new TextViewBuilder(getContext(), match_latest_values[i].name) + .align_center() + .size(30) + .padding(0,0,20,5) + .build() + ); if(data[i] != null) match_latest_values[i].add_history_view(binding.matchArea, data[i]); } } + } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/EventFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/EventFragment.java index 87c067e..2d7589b 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/EventFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/EventFragment.java @@ -30,6 +30,7 @@ import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.frcMatch; import com.ridgebotics.ridgescout.utility.SettingsManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Arrays; @@ -59,11 +60,10 @@ public class EventFragment extends Fragment { add_match_scouting(event); } private void addTableText(TableRow tr, String textStr){ - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center - text.setText(textStr); - tr.addView(text); + tr.addView(new TextViewBuilder(getContext(), textStr) + .align_center() + .size(18) + .build()); } public void add_pit_scouting(frcEvent event){ @@ -94,33 +94,32 @@ public class EventFragment extends Fragment { tr = new TableRow(getContext()); } - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + TextViewBuilder text = new TextViewBuilder(getContext(), String.valueOf(num)) + .size(18) + .align_center(); - text.setText(String.valueOf(num)); final String filename = event.eventCode + "-" + num + ".pitscoutdata"; if(FileEditor.fileExist(filename)){ final boolean[] rescout = {DataManager.rescout_list.contains(filename)}; - text.setBackgroundColor(rescout[0] ? color_rescout : color_found); + text.tv.setBackgroundColor(rescout[0] ? color_rescout : color_found); - text.setOnLongClickListener(view -> { + text.tv.setOnLongClickListener(view -> { rescout[0] = !rescout[0]; if(rescout[0]) { - text.setBackgroundColor(color_rescout); + text.tv.setBackgroundColor(color_rescout); DataManager.rescout_list.add(filename); }else{ - text.setBackgroundColor(color_found); + text.tv.setBackgroundColor(color_found); DataManager.rescout_list.remove(filename); } DataManager.save_rescout_list(); return true; }); }else{ - text.setBackgroundColor(color_not_found); + text.tv.setBackgroundColor(color_not_found); } - tr.addView(text); + tr.addView(text.build()); } if(tr != null) binding.teamsTable.addView(tr); @@ -153,10 +152,6 @@ public class EventFragment extends Fragment { addTableText(tr, String.valueOf(match.matchIndex)); // for(int i=0;i<6;i++){ - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - int team_num; String alliance_position; @@ -168,20 +163,23 @@ public class EventFragment extends Fragment { alliance_position = "blue-"+(i-2); } - text.setText(String.valueOf(team_num)); + TextViewBuilder text = new TextViewBuilder(getContext(), String.valueOf(team_num)) + .size(18) + .align_center(); + final String filename = event.eventCode + "-" + match.matchIndex + "-" + alliance_position + "-" + team_num + ".matchscoutdata"; if(FileEditor.fileExist(filename)){ final boolean[] rescout = {DataManager.rescout_list.contains(filename)}; - text.setBackgroundColor(rescout[0] ? color_rescout : color_found); + text.tv.setBackgroundColor(rescout[0] ? color_rescout : color_found); - text.setOnLongClickListener(view -> { + text.tv.setOnLongClickListener(view -> { rescout[0] = !rescout[0]; if(rescout[0]) { - text.setBackgroundColor(color_rescout); + text.tv.setBackgroundColor(color_rescout); DataManager.rescout_list.add(filename); }else{ - text.setBackgroundColor(color_found); + text.tv.setBackgroundColor(color_found); DataManager.rescout_list.remove(filename); } DataManager.save_rescout_list(); @@ -189,9 +187,9 @@ public class EventFragment extends Fragment { }); }else{ - text.setBackgroundColor(color_not_found); + text.tv.setBackgroundColor(color_not_found); } - tr.addView(text); + tr.addView(text.build()); } binding.matchTable.addView(tr); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/MatchScoutingFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/MatchScoutingFragment.java index be0acb8..21457f7 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/MatchScoutingFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/MatchScoutingFragment.java @@ -21,6 +21,7 @@ import androidx.fragment.app.Fragment; import com.google.android.material.divider.MaterialDivider; import com.ridgebotics.ridgescout.ui.views.ToggleTitleView; +import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.databinding.FragmentScoutingMatchBinding; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; @@ -32,6 +33,7 @@ import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.AutoSaveManager; import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.FileEditor; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; // Fragment for match scouting data editing. public class MatchScoutingFragment extends Fragment { @@ -58,10 +60,12 @@ public class MatchScoutingFragment extends Fragment { binding.matchTeamCard.setVisibility(View.VISIBLE); if(DataManager.match_values == null || DataManager.match_values.length == 0){ - TextView tv = new TextView(getContext()); - tv.setText("Failed to load fields.\nTry to either download or create match scouting fields."); - tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - binding.MatchScoutArea.addView(tv); + + binding.MatchScoutArea.addView( + new TextViewBuilder(getContext(), "Failed to load fields.\nTry to either download or create match scouting fields.") + .align_center() + .build()); + return binding.getRoot(); } @@ -347,7 +351,7 @@ public class MatchScoutingFragment extends Fragment { - public void get_fields(){ + public void get_fields() throws BuiltByteParser.byteParsingExeption{ ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, DataManager.match_values, DataManager.match_transferValues); RawDataType[] types = psdr.data.array; diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/PitScoutingFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/PitScoutingFragment.java index 6c1c194..9acdb05 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/PitScoutingFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/PitScoutingFragment.java @@ -20,8 +20,10 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.google.android.material.divider.MaterialDivider; +import com.ridgebotics.ridgescout.ui.views.PitScoutingIndicator; import com.ridgebotics.ridgescout.ui.views.ToggleTitleView; import com.ridgebotics.ridgescout.utility.AlertManager; +import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.databinding.FragmentScoutingPitBinding; import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; @@ -76,7 +78,6 @@ public class PitScoutingFragment extends Fragment { String fileUsernames = ""; ToggleTitleView[] titles; - AutoSaveManager asm = new AutoSaveManager(this::save, AUTO_SAVE_DELAY); ArrayList rawDataTypes; @@ -99,7 +100,7 @@ public class PitScoutingFragment extends Fragment { } public void set_indicator_color(int color){ - binding.pitFileIndicator.setBackgroundColor(color); + binding.pitIndicator.setColor(color); } public void update_asm(){ @@ -114,10 +115,9 @@ public class PitScoutingFragment extends Fragment { public void loadTeam(){ // clear_fields(); - binding.pitFileIndicator.setVisibility(View.VISIBLE); binding.pitsTeamCard.setVisibility(View.VISIBLE); - binding.pitBarTeamNum.setText(String.valueOf(team.teamNumber)); - binding.pitUsername.setText(SettingsManager.getUsername()); + binding.pitIndicator.setTeamNum(team.teamNumber); + binding.pitIndicator.setUsername(SettingsManager.getUsername()); binding.pitsTeamCard.fromTeam(team); filename = evcode + "-" + team.teamNumber + ".pitscoutdata"; @@ -146,7 +146,7 @@ public class PitScoutingFragment extends Fragment { } } - binding.pitFileIndicator.bringToFront(); + binding.pitIndicator.bringToFront(); asm.start(); @@ -154,7 +154,7 @@ public class PitScoutingFragment extends Fragment { private void enableRescoutButton(){ set_indicator_color(rescout ? rescout_color : saved_color); - binding.pitFileIndicator.setOnLongClickListener(v -> { + binding.pitIndicator.setOnLongClickListener(v -> { rescout = !rescout; if(rescout){ set_indicator_color(rescout_color); @@ -171,7 +171,7 @@ public class PitScoutingFragment extends Fragment { } private void disableRescoutButton(){ - binding.pitFileIndicator.setOnLongClickListener(null); + binding.pitIndicator.setOnLongClickListener(null); } @@ -225,7 +225,7 @@ public class PitScoutingFragment extends Fragment { } } - public void get_fields(){ + public void get_fields() throws BuiltByteParser.byteParsingExeption{ ScoutingDataWriter.ParsedScoutingDataResult psdr = ScoutingDataWriter.load(filename, pit_values, pit_transferValues); RawDataType[] types = psdr.data.array; diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/ScoutingFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/ScoutingFragment.java index 47532e0..e6ddc67 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/ScoutingFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/scouting/ScoutingFragment.java @@ -15,6 +15,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -27,6 +28,7 @@ import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.SettingsManager; import com.ridgebotics.ridgescout.databinding.FragmentScoutingBinding; import com.ridgebotics.ridgescout.utility.DataManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Set; @@ -89,7 +91,7 @@ public class ScoutingFragment extends Fragment { binding.textMatchAlliance.setVisibility(View.GONE); binding.textName.setVisibility(View.GONE); - binding.textNextMatch.setVisibility(View.GONE); + binding.infoBox.setVisibility(View.GONE); binding.textRescoutIndicator.setVisibility(View.GONE); binding.matchScoutingButton.setEnabled(false); @@ -123,20 +125,7 @@ public class ScoutingFragment extends Fragment { findNavController(this).navigate(R.id.action_navigation_scouting_to_navigation_scouting_event); }); - - binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!"); - - int matchNum = SettingsManager.getMatchNum(); - int nextMatch = -1; - try { - nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex; - } catch (Exception e){ - AlertManager.error(e); - } - - binding.textNextMatch.setText("Our next match: Match " + nextMatch); - binding.textMatchAlliance.setText("Match: " + (matchNum+1) + ", " + SettingsManager.getAllyPos()); - binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size()); + updateDashboard(); return binding.getRoot(); } @@ -165,4 +154,31 @@ public class ScoutingFragment extends Fragment { }); } + private void updateDashboard() { + binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!"); + + int curMatchNum = SettingsManager.getMatchNum(); + int nextMatch; + int teamNum = SettingsManager.getTeamNum(); + try { + nextMatch = event.getNextTeamMatch(teamNum, curMatchNum).matchIndex; + } catch (Exception e){ + AlertManager.error("Sorry, in event ("+evcode+"), your team number ("+teamNum+") wasn't found!", e); + return; + } + + binding.textMatchAlliance.setText("Match: " + (curMatchNum+1) + ", " + SettingsManager.getAllyPos()); + binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size()); + + binding.infoBox.addView(new TextViewBuilder(getContext(), "Our next match: Match " + nextMatch) + .body1() + .build()); + + int informedBy = event.getMostInformedBy(teamNum, curMatchNum); + + + binding.infoBox.addView(new TextViewBuilder(getContext(), "Most informed by: Match " + informedBy) + .body1() + .build()); + } } \ No newline at end of file diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldEditorHelper.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldEditorHelper.java index c04da59..018a9ad 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldEditorHelper.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldEditorHelper.java @@ -19,6 +19,7 @@ import com.ridgebotics.ridgescout.types.input.SliderType; import com.ridgebotics.ridgescout.types.input.TallyType; import com.ridgebotics.ridgescout.types.input.TextType; import com.ridgebotics.ridgescout.utility.AlertManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.lang.reflect.Field; import java.util.UUID; @@ -397,11 +398,11 @@ public class FieldEditorHelper { this.t = t; views = new View[types.length]; for(int i = 0; i < types.length; i++){ - TextView tv = new TextView(c); - tv.setText(types[i].name); - tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - tv.setTextSize(20); - parentView.addView(tv); + + parentView.addView(new TextViewBuilder(c, types[i].name) + .align_center() + .size(20) + .build()); views[i] = createEdit(c, types[i]); parentView.addView(views[i]); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldsFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldsFragment.java index b738e28..85df7da 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldsFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/FieldsFragment.java @@ -21,6 +21,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.navigation.Navigation; +import com.google.android.material.button.MaterialButton; import com.ridgebotics.ridgescout.MainActivity; import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.databinding.FragmentSettingsFieldsBinding; @@ -29,6 +30,7 @@ import com.ridgebotics.ridgescout.types.input.FieldType; import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView; import com.ridgebotics.ridgescout.ui.views.FieldDisplay; import com.ridgebotics.ridgescout.utility.AlertManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Collections; @@ -195,10 +197,9 @@ public class FieldsFragment extends Fragment { sv.addView(table); - TextView UUID = new TextView(getContext()); - UUID.setText("Type: " + field.get_type_name() + "\nUUID: " + field.UUID); - table.addView(UUID); + table.addView(new TextViewBuilder(getContext(), "Type: " + field.get_type_name() + "\nUUID: " + field.UUID) + .build()); FieldEditorHelper f = new FieldEditorHelper(getContext(), field, table); @@ -216,7 +217,7 @@ public class FieldsFragment extends Fragment { AlertDialog dialog = alert.create(); dialog.show(); - Button deleteButton = new Button(getContext()); + MaterialButton deleteButton = new MaterialButton(getContext()); deleteButton.setText("DELETE"); deleteButton.setOnClickListener(l -> { AlertDialog.Builder alert2 = new AlertDialog.Builder(getContext()); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/SettingsFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/SettingsFragment.java index 7e47aca..926d0fe 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/SettingsFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/settings/SettingsFragment.java @@ -1,6 +1,7 @@ package com.ridgebotics.ridgescout.ui.settings; -import static android.view.View.VISIBLE; +import static android.widget.LinearLayout.HORIZONTAL; +import static android.widget.LinearLayout.VERTICAL; import static androidx.navigation.fragment.FragmentKt.findNavController; import static com.ridgebotics.ridgescout.utility.SettingsManager.AllyPosKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.CustomEventsKey; @@ -13,11 +14,15 @@ import static com.ridgebotics.ridgescout.utility.SettingsManager.UnameKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.WifiModeKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.YearNumKey; import static com.ridgebotics.ridgescout.utility.SettingsManager.defaults; +import static com.ridgebotics.ridgescout.utility.SettingsManager.getEVCode; import static com.ridgebotics.ridgescout.utility.SettingsManager.getEditor; import static com.ridgebotics.ridgescout.utility.SettingsManager.prefs; import android.app.AlertDialog; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.InputType; @@ -26,7 +31,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -36,7 +40,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.google.android.material.button.MaterialButton; import com.google.android.material.checkbox.MaterialCheckBox; +import com.google.android.material.divider.MaterialDivider; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; import com.ridgebotics.ridgescout.R; @@ -44,9 +50,11 @@ import com.ridgebotics.ridgescout.databinding.FragmentSettingsBinding; import com.ridgebotics.ridgescout.scoutingData.Fields; import com.ridgebotics.ridgescout.ui.views.CustomSpinnerView; import com.ridgebotics.ridgescout.ui.views.TallyCounterView; +import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.FileEditor; -import com.ridgebotics.ridgescout.utility.SettingsManager; +import com.ridgebotics.ridgescout.utility.ToDelete; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Arrays; @@ -65,20 +73,6 @@ public class SettingsFragment extends Fragment { reloadSettings(); - binding.fieldsButton.setOnClickListener(v -> { - binding.fieldsButton.setEnabled(false); - binding.fieldsButtons.setVisibility(VISIBLE); - }); - - binding.fieldsMatchesButton.setOnClickListener(v -> { - FieldsFragment.set_filename(Fields.matchFieldsFilename); - findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); - }); - - binding.fieldsPitsButton.setOnClickListener(v -> { - FieldsFragment.set_filename(Fields.pitsFieldsFilename); - findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); - }); return root; @@ -91,28 +85,77 @@ public class SettingsFragment extends Fragment { SettingsManager manager = new SettingsManager(getContext()); + ButtonSettingsItem appInfoButton = new ButtonSettingsItem(); + appInfoButton.addButton("App info", v -> showAppInfo()); + manager.addItem(appInfoButton); + + + ButtonSettingsItem corruptButton = new ButtonSettingsItem(); + corruptButton.addButton("find corrupted files", view -> { + ToDelete.findCorruptedFiles(getContext()); + }); + corruptButton.addButton("delete files", view -> { + ToDelete.deleteFiles(getContext(), Arrays.asList(FileEditor.getFiles()), false); + }); +// corruptButton.setEnabled(!getEVCode().equals("unset")); + + manager.addItem(corruptButton); + manager.addItem(new HeaderSettingsItem("Advanced")); + + + + + ButtonSettingsItem fieldsButtons = new ButtonSettingsItem(); + fieldsButtons.addButton("Edit pit fields", v -> { + FieldsFragment.set_filename(Fields.pitsFieldsFilename); + findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); + }); + fieldsButtons.addButton("Edit match fields", v -> { + FieldsFragment.set_filename(Fields.matchFieldsFilename); + findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); + }); + manager.addItem(fieldsButtons); + + ButtonSettingsItem noticeButton = new ButtonSettingsItem(); + noticeButton.addButton("Edit scout notice", v->editNotice()); + noticeButton.setEnabled(!getEVCode().equals("unset")); + manager.addItem(noticeButton); + + manager.addItem(new CheckboxSettingsItem(CustomEventsKey, "Custom Events")); + CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "[⚠] Send meta files"); + manager.addItem(FTPSendMetaFiles); + + manager.addItem(new HeaderSettingsItem("Admin")); + + + + StringSettingsItem FTPKey = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPKey, "Sync Key"); manager.addItem(FTPKey); StringSettingsItem FTPServer = new StringSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPServer, "Sync Server (Sync)"); manager.addItem(FTPServer); - CheckboxSettingsItem FTPSendMetaFiles = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPSendMetaFiles, "⚠ Send meta files"); - manager.addItem(FTPSendMetaFiles); - CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "FTP Enabled", FTPServer, FTPKey, FTPSendMetaFiles); + CheckboxSettingsItem FTPEnabled = new CheckboxSettingsItem(com.ridgebotics.ridgescout.utility.SettingsManager.FTPEnabled, "Sync Enabled", FTPServer, FTPKey, FTPSendMetaFiles); manager.addItem(FTPEnabled); manager.addItem(new CheckboxSettingsItem(WifiModeKey, "Wifi Mode", FTPEnabled)); - manager.addItem(new CheckboxSettingsItem(EnableQuickAllianceChangeKey, "Enable quick alliance swap", null)); + + manager.addItem(new 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[]{ "2025", "2025 (Flipped)" })); - manager.addItem(new NumberSettingsItem(YearNumKey, "Year", 0, 9999)); - - manager.addItem(new DropdownSettingsItem(AllyPosKey, "Alliance Pos", alliance_pos_list)); int max = 0; @@ -134,16 +177,48 @@ public class SettingsFragment extends Fragment { manager.addItem(new StringSettingsItem(UnameKey, "Username")); manager.addItem(new NumberSettingsItem(TeamNumKey, "Team Number", 0, 99999)); + manager.addItem(new HeaderSettingsItem("Scouting")); + + + + binding.SettingsTable.removeAllViews(); + manager.getView(binding.SettingsTable); - if(!DataManager.getevcode().equals("unset")){ - Button editNoticeButton = new Button(getContext()); - editNoticeButton.setText("Edit Scout Notice"); - binding.SettingsTable.addView(editNoticeButton); - editNoticeButton.setOnClickListener(v->editNotice()); - } + + // Add "Edit scout notice" button to the bottom of the page= + + + // Add "Field edit" buttons to the bottom of the page +// LinearLayout fieldButtons = new LinearLayout(getContext()); +// fieldButtons.setLayoutParams(new LinearLayout.LayoutParams( +// LinearLayout.LayoutParams.MATCH_PARENT, +// LinearLayout.LayoutParams.WRAP_CONTENT +// )); +// fieldButtons.setOrientation(HORIZONTAL); +// +// Button editMatchFieldsButton = new Button(getContext()); +// editMatchFieldsButton.setText("Edit Match Fields"); +// editMatchFieldsButton.setOnClickListener(v -> { +// FieldsFragment.set_filename(Fields.matchFieldsFilename); +// findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); +// }); +// fieldButtons.addView(editMatchFieldsButton); +// +// +// Button editPitsFieldsButton = new Button(getContext()); +// editPitsFieldsButton.setText("Edit pits Fields"); +// editPitsFieldsButton.setOnClickListener(v -> { +// FieldsFragment.set_filename(Fields.pitsFieldsFilename); +// findNavController(this).navigate(R.id.action_navigation_settings_to_navigation_data_fields); +// }); +// fieldButtons.addView(editPitsFieldsButton); + + +// binding.SettingsTable.addView(fieldButtons); + } @@ -178,6 +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(); + } + + + @@ -284,9 +394,8 @@ public class SettingsFragment extends Fragment { @Override public View createView(Context context) { - TextView titleView = new TextView(context); - titleView.setText(getTitle()); - titleView.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1); + TextView titleView = new TextViewBuilder(context, getTitle()) + .sub1().build(); TextInputLayout textInputLayout = new TextInputLayout(context); editText = new TextInputEditText(context); @@ -347,7 +456,7 @@ public class SettingsFragment extends Fragment { @Override public View createView(Context context) { LinearLayout ll = new LinearLayout(getContext()); - ll.setOrientation(LinearLayout.VERTICAL); + ll.setOrientation(VERTICAL); tally = new TallyCounterView(getContext()); @@ -366,11 +475,10 @@ public class SettingsFragment extends Fragment { }); tally.setEnabled(enabled); - TextView tv = new TextView(getContext()); - tv.setText(getTitle()); - tv.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline6); - tv.setGravity(Gravity.CENTER); - ll.addView(tv); + + ll.addView(new TextViewBuilder(getContext(), getTitle()) + .h6() + .build()); ll.addView(tally); @@ -475,6 +583,93 @@ public class SettingsFragment extends Fragment { } } + public class HeaderSettingsItem extends SettingsItem { + 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 { + List buttons = new ArrayList<>(); + List titles = new ArrayList<>(); + List callbacks = new ArrayList<>(); + + boolean enabled = true; + + public ButtonSettingsItem() { + super("", "", null); + } + + @Override + public void setEnabled(boolean enabled){ + this.enabled = enabled; + for(int i = 0; i < buttons.size(); i++){ + buttons.get(i).setEnabled(enabled); + } + } + + public void addButton(String text, View.OnClickListener onClickListener) { + titles.add(text); + callbacks.add(onClickListener); + } + + @Override + public View createView(Context context) { + LinearLayout ll = new LinearLayout(context); + ll.setOrientation(HORIZONTAL); + + for(int i = 0; i < titles.size(); i++){ + MaterialButton button = new MaterialButton(context); + button.setText(titles.get(i)); + button.setOnClickListener(callbacks.get(i)); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f); + layoutParams.setMargins(3, 0, 3, 0); +// layoutParams.weight + button.setLayoutParams(layoutParams); + button.setEnabled(enabled); +// button.weight + buttons.add(button); + + ll.addView(button); + } + + return ll; + } + + @Override + public Void getValue() { + return null; + } + } + public class SettingsManager { private Context context; private HashMap settings; @@ -497,7 +692,7 @@ public class SettingsFragment extends Fragment { items.add(item); LinearLayout itemContainer = new LinearLayout(context); - itemContainer.setOrientation(LinearLayout.VERTICAL); + itemContainer.setOrientation(VERTICAL); itemContainer.setPadding(32, 0, 32, 8); View view = item.createView(context); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FTPSync.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FTPSync.java deleted file mode 100644 index d042ebe..0000000 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FTPSync.java +++ /dev/null @@ -1,272 +0,0 @@ -package com.ridgebotics.ridgescout.ui.transfer; - -import static com.ridgebotics.ridgescout.utility.DataManager.evcode; -import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir; - -import android.util.Log; - -import com.ridgebotics.ridgescout.utility.AlertManager; -import com.ridgebotics.ridgescout.utility.BuiltByteParser; -import com.ridgebotics.ridgescout.utility.ByteBuilder; -import com.ridgebotics.ridgescout.utility.FileEditor; -import com.ridgebotics.ridgescout.utility.SettingsManager; - -import org.apache.commons.net.ftp.FTP; -import org.apache.commons.net.ftp.FTPClient; -import org.apache.commons.net.ftp.FTPFile; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -// This is now deprecated -// Class to synchronise data over FTP. -public class FTPSync extends Thread { - public static final String remoteBasePath = "/RidgeScout/"; - public static final String timestampsFilename = "timestamps"; - - - - public static long lastSyncTime = 0; - private static Date curSyncTime; - private static final long millisTolerance = 1000; - - private boolean after(Date a, Date b){ - return a.getTime() - b.getTime() > millisTolerance; - } - - - public interface onResult { - void onResult(boolean error, int upCount, int downCount); - } - public interface UpdateIndicator { - void onText(String text); - } - private static UpdateIndicator updateIndicator = text -> {}; - public static String text = ""; - private static void setUpdateIndicator(String m_text){ - text = m_text; - updateIndicator.onText(m_text); - } - public static void setOnUpdateIndicator(UpdateIndicator m_updateIndicator){ - updateIndicator = m_updateIndicator; - } - - private static onResult onResult = (error, upCount, downCount) -> {}; - public static void setOnResult(onResult result){ - onResult = result; - } - - private static boolean isRunning = false; - public static boolean getIsRunning(){return isRunning;} - - public static void sync(){ -// DataManager.reload_event(); - FTPSync ftpSync = new FTPSync(); - - curSyncTime = new Date(); - - ftpSync.start(); - } - - FTPClient ftpClient; - - private int upCount = 0; - private int downCount = 0; - - private void downloadFile(String remoteFile, File localFile) throws IOException { - try (FileOutputStream fos = new FileOutputStream(localFile)) { - ftpClient.retrieveFile(remoteBasePath + remoteFile, fos); - } - } - - private void uploadFile(File localFile) throws IOException { - try (FileInputStream fis = new FileInputStream(localFile)) { - ftpClient.storeFile(remoteBasePath + localFile.getName(), fis); - } - } - - private FTPFile findRemoteFile(FTPFile[] remoteFiles, String fileName) { - for (FTPFile file : remoteFiles) { - if (file.getName().equals(fileName)) { - return file; - } - } - return null; - } - - private Date getUtcTimestamp(FTPFile file) { - return file.getTimestamp().getTime(); - } - - private Date getLocalFileUtcTimestamp(File file) { - return new Date(file.lastModified()); - } - - private void setLocalFileTimestamp(File file, Date date) { - file.setLastModified(date.getTime()); - } - - - public void run() { - isRunning = true; - boolean sendMetaFiles = SettingsManager.getFTPSendMetaFiles(); - - // Meta files - List 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 remoteTimestamps = getTimestamps(); - - // Loop through local files and send all that are more recent - if (localFiles != null) { - for (int i = 0; i < localFiles.length; i++) { - File localFile = localFiles[i]; - setUpdateIndicator("Uploading " + (i+1) + "/" + localFiles.length); - - if(localFile.isDirectory()) continue; - // Remove timestamts file - if(localFile.getName().equals(timestampsFilename)) continue; - // Remove meta files if the option is disabled - if(!sendMetaFiles && meta_string_array.contains(localFile.getName())) continue; - - Date remoteTimestamp = remoteTimestamps.get(localFile.getName()); - - Date localTimeStamp = getLocalFileUtcTimestamp(localFile); - - if (remoteTimestamp == null || after(localTimeStamp, remoteTimestamp)) { - uploadFile(localFile); - Log.i(getClass().toString(), "Uploaded" + localFile.getName()); - - setLocalFileTimestamp(localFile, curSyncTime); - remoteTimestamps.put(localFile.getName(), curSyncTime); - upCount++; - }else{ - Log.i(getClass().toString(), "Did not upload"); - } - } - } - - Set keySet = remoteTimestamps.keySet(); - Iterator keyIt = keySet.iterator(); - for (int i = 0; i < keySet.size(); i++) { - String remoteFile = keyIt.next(); - setUpdateIndicator("Downloading " + (i+1) + "/" + keySet.size()); - - File localFile = new File(baseDir, remoteFile); - if(remoteFile.equals(timestampsFilename)) continue; - // Remove meta files if the option is disabled - if(!sendMetaFiles && meta_string_array.contains(remoteFile)) continue; - -// Date t1 = getLocalFileUtcTimestamp(localFile); -// Date t2 = getUtcTimestamp(remoteFile); -//// -// System.out.println("- " + t1 + (t1.after(t2) ? ">" : "<") + t2); - - Date localTimeStamp = getLocalFileUtcTimestamp(localFile); - Date remoteTimestamp = remoteTimestamps.get(remoteFile); - - - - if (!localFile.exists() || (after(remoteTimestamp, localTimeStamp) && !localTimeStamp.equals(remoteTimestamp))) { - downloadFile(remoteFile, localFile); - - Log.i(getClass().toString(), "Downloaded " + localFile.getName()); - - if(!localFile.exists()) Log.i(getClass().toString(), "Not exist"); - else if(after(remoteTimestamp, localTimeStamp)) Log.i(getClass().toString(), "Before: " + (localTimeStamp.getTime()-remoteTimestamp.getTime())); - -// Date d = getUtcTimestamp(remoteFile); - setLocalFileTimestamp(localFile, remoteTimestamps.get(localFile.getName())); -// remoteTimestamps.put(remoteFile, curSyncTime); - downCount++; - }else{ - Log.i(getClass().toString(), "Did not download"); - } - } - - setTimestamps(remoteTimestamps); - - } catch (Exception e) { - AlertManager.error("Failed Syncing!", e); - onResult.onResult(true, upCount, downCount); - setUpdateIndicator("ERROR!"); - } finally { - onResult.onResult(false, upCount, downCount); - setUpdateIndicator("Finished"); - } - - isRunning = false; - } - - private boolean setTimestamps(Map 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 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 pa = bbp.parse(); - - Map 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<>(); - } - } -} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FileSelectorFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FileSelectorFragment.java index 64b77ea..915b335 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FileSelectorFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FileSelectorFragment.java @@ -24,6 +24,7 @@ import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.ByteBuilder; import com.ridgebotics.ridgescout.utility.DataManager; import com.ridgebotics.ridgescout.utility.FileEditor; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import java.util.ArrayList; import java.util.Arrays; @@ -49,7 +50,10 @@ public class FileSelectorFragment extends Fragment { meta_string_array = new String[]{ "matches.fields", "pits.fields", - evcode+".eventdata" + evcode+".eventdata", + evcode+".rescout", + evcode+".scoutnotice", + "todelete.colabarray", }; String[] files = FileEditor.getEventFiles(evcode); @@ -74,10 +78,12 @@ public class FileSelectorFragment extends Fragment { checkBox.setChecked(true); tr.addView(checkBox); - TextView tv = new TextView(getContext()); - tv.setText(String.valueOf(files[i])); - tv.setTextSize(20); - tr.addView(tv); + // Filename + tr.addView( + new TextViewBuilder(getContext(), files[i]) + .size(20) + .build() + ); final int fi = i; tr.setOnClickListener(view -> { diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/HttpSync.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/HttpSync.java index f70b35b..2ef35a3 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/HttpSync.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/HttpSync.java @@ -4,14 +4,14 @@ import static com.ridgebotics.ridgescout.utility.FileEditor.baseDir; import android.util.Log; +import com.ridgebotics.ridgescout.types.ColabArray; import com.ridgebotics.ridgescout.utility.AlertManager; -import com.ridgebotics.ridgescout.utility.BuiltByteParser; -import com.ridgebotics.ridgescout.utility.ByteBuilder; import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.HttpGetFile; import com.ridgebotics.ridgescout.utility.HttpPutFile; import com.ridgebotics.ridgescout.utility.RequestTask; import com.ridgebotics.ridgescout.utility.SettingsManager; +import com.ridgebotics.ridgescout.utility.ToDelete; import org.json.JSONException; import org.json.JSONObject; @@ -23,15 +23,12 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -// This is now deprsicated -// Class to syncronise data over FTP. +// Class to synchronise data over HTTP. public class HttpSync extends Thread { public static final String timestampsFilename = "timestamps"; @@ -101,6 +98,10 @@ public class HttpSync extends Thread { public void run() { isRunning = true; boolean sendMetaFiles = SettingsManager.getFTPSendMetaFiles(); + + ToDelete.reload_todelete_list(); + List removeFiles = ToDelete.todelete_list.get(); + String serverIP = SettingsManager.getFTPServer(); String serverKey = SettingsManager.getFTPKey(); @@ -118,6 +119,9 @@ public class HttpSync extends Thread { getLocalFileMetadata(); + localFiles.removeIf(localFile -> removeFiles.contains(localFile.filename+","+localFile.checksum)); + remoteFiles.removeIf(remoteFile -> removeFiles.contains(remoteFile.filename+","+remoteFile.checksum)); + @@ -130,23 +134,30 @@ public class HttpSync extends Thread { TransferFile remoteFile = findInFileArray(remoteFiles, localFile.filename); + // Check if the file is a meta file, and uploads it based off of the setting + boolean sendField = (sendMetaFiles || !(localFile.filename.endsWith(".fields"))); + boolean shouldUpload; + boolean special; - if( - ( - sendMetaFiles || !( - localFile.filename.endsWith(".fields") - ) + // If there is no file on the sever, upload. + if(remoteFile == null) { + shouldUpload = true; + special = false; + } + else { + // If the remote file is the same as the local one, do nothing. + boolean checksumsEqual = Objects.equals(localFile.checksum, remoteFile.checksum); + // If the local file is a colabarray, give it a special propreties + special = FileEditor.requiresSpecialInteraction(remoteFile.filename); + // If the local file is updated after the remote file + boolean after = after(localFile.updated, remoteFile.updated); - ) - && (remoteFile == null || - ( - !Objects.equals(localFile.checksum, remoteFile.checksum) && - after(localFile.updated, remoteFile.updated) - ) - )) { - uploadFile(localFile, serverIP, serverKey); -// await(); + shouldUpload = !checksumsEqual && (special || after); + } + + if(sendField && shouldUpload) { + uploadFile(localFile, serverIP, serverKey, special); Log.d(getClass().toString(), "LocalFile: " + localFile.filename + ", " + localFile.checksum + ", " + localFile.updated + ": Uploaded"); upCount++; }else { @@ -163,13 +174,23 @@ public class HttpSync extends Thread { TransferFile localFile = findInFileArray(localFiles, remoteFile.filename); - if(localFile == null || - ( - !Objects.equals(localFile.checksum, remoteFile.checksum) && - after(remoteFile.updated, localFile.updated) && - !localFile.updated.equals(remoteFile.updated) - ) - ) { + boolean shouldUpload; + + // If there is no file on the sever, upload. + if(localFile == null) { + shouldUpload = true; + } else { + // If the remote file is the same as the local one, do nothing. + boolean checksumsEqual = !Objects.equals(localFile.checksum, remoteFile.checksum); + // If the local file is updated after the remote file + boolean after = after(remoteFile.updated, localFile.updated); + // If the local file and remote file's upload dates are exactly the same + boolean datesEqual = !localFile.updated.equals(remoteFile.updated); + + shouldUpload = (!checksumsEqual && (after) && !datesEqual); + } + + if(shouldUpload) { downloadFile(remoteFile, serverIP); // await(); Log.d(getClass().toString(), "RemoteFile: " + remoteFile.filename + ", " + remoteFile.checksum + ", " + remoteFile.updated + ": Downloaded"); @@ -182,8 +203,8 @@ public class HttpSync extends Thread { setUpdateIndicator("Downloading " + (Math.floor((double) (i * 1000) / remoteFiles.size()) / 10) + "%"); } - - + // Remove files marked for deletion + ToDelete.deleteFiles(); setUpdateIndicator("Finished, " + upCount + " Up, " + downCount + " Down"); @@ -193,6 +214,7 @@ public class HttpSync extends Thread { } + // Find file based off of filename private TransferFile findInFileArray(List files, String filename){ for(TransferFile file : files) { if(file.filename.equals(filename)) @@ -201,29 +223,12 @@ public class HttpSync extends Thread { return null; } + // Get teh last modified date of a file private Date getLocalFileUtcTimestamp(File file) { return new Date(file.lastModified()); } - public static String getSHA256Hash(String filePath) throws IOException, NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - FileInputStream fis = new FileInputStream(filePath); - byte[] byteArray = new byte[1024]; - int bytesCount = 0; - - while ((bytesCount = fis.read(byteArray)) != -1) { - digest.update(byteArray, 0, bytesCount); - } - fis.close(); - - byte[] bytes = digest.digest(); - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - + // Load the local metadata of files private void getLocalFileMetadata() { File localDir = new File(baseDir); File[] localFileNames = localDir.listFiles(); @@ -240,9 +245,10 @@ public class HttpSync extends Thread { tf.filename = file.getName(); tf.updated = getLocalFileUtcTimestamp(file); try { - tf.checksum = getSHA256Hash(file.getPath()); + tf.checksum = FileEditor.getSHA256Hash(file.getName()); } catch (Exception e) { - + AlertManager.error("Failed to get hash of: " + file.getName(), e); + continue; } localFiles.add(tf); } @@ -278,90 +284,100 @@ public class HttpSync extends Thread { await(); } - -// private boolean setTimestamps(Map 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 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 pa = bbp.parse(); -// -// Map output = new HashMap<>(); -// for(int i = 0; i < pa.size(); i+=2){ -//// System.out.println((long) pa.get(i).get()); -// output.put( -// (String) pa.get(i).get(), -// new Date((long) pa.get(i+1).get()) -// ); -// } -// return output; -// -// }catch (IOException | BuiltByteParser.byteParsingExeption e){ -// AlertManager.error("Failed Syncing!", e); -// return new HashMap<>(); -// } -// } - void uploadFile(TransferFile tf, String serverURL, String apiKey) { + // Create HTTP request to upload file + void uploadFile(TransferFile tf, String serverURL, String apiKey, boolean special) { runningRequest.set(false); - HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), new HttpPutFile.UploadCallback() { - @Override - public void onResult(String error) { - if(error != null) + + + // If the file is "special", download the server copy and merge the local and remote ColabArrays + if(special) { + HttpGetFile getTask = new HttpGetFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), (stream, error) -> { + if(error != null) { + AlertManager.error(error); + return; + } else if (stream == null) { + AlertManager.error("Output stream from download was null!"); + return; + } + + byte[] bytes = stream.toByteArray(); + + FileEditor.syncColabArray( + tf.filename, + FileEditor.readFile(tf.filename), + bytes + ); + + + HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), error2 -> { + if (error2 != null) + AlertManager.error(error2); + runningRequest.set(true); + }, new String[]{ + "api_key: " + apiKey, + ("modified: " + tf.updated.getTime()) + }); + + uploadTask.execute(); + + + + }); + + getTask.execute(); + + + } else { + // Upload the file + HttpPutFile uploadTask = new HttpPutFile(serverURL + "/api/" + tf.filename, new File(baseDir + tf.filename), error -> { + if (error != null) AlertManager.error(error); runningRequest.set(true); - } - }, new String[]{ - "api_key: " + apiKey, - ("modified: " + tf.updated.getTime()) - }); // Pass auth token if needed + }, new String[]{ + "api_key: " + apiKey, + ("modified: " + tf.updated.getTime()) + }); // Pass auth token if needed - uploadTask.execute(); - await(); + uploadTask.execute(); + await(); + } } private void setLocalFileTimestamp(File file, Date date) { file.setLastModified(date.getTime()); } + + // Download a file from the remote server void downloadFile(TransferFile tf, String serverURL) { runningRequest.set(false); File f = new File(baseDir + tf.filename); - HttpGetFile uploadTask = new HttpGetFile(serverURL + "/api/" + tf.filename, f, new HttpGetFile.DownloadCallback() { - @Override - public void onResult(String error) { - if(error != null) - AlertManager.error(error); - else - setLocalFileTimestamp(f, tf.updated); - runningRequest.set(true); - + HttpGetFile uploadTask = new HttpGetFile(serverURL + "/api/" + tf.filename, f, (stream, error) -> { + if(error != null) { + AlertManager.error(error); + return; + } else if (stream == null) { + AlertManager.error("Output stream from download was null!"); + return; } - }); // Pass auth token if needed + + byte[] bytes = stream.toByteArray(); + + if(FileEditor.requiresSpecialInteraction(tf.filename)) { + FileEditor.syncColabArray( + tf.filename, + FileEditor.readFile(tf.filename), + bytes + ); + } else { + FileEditor.writeFile(tf.filename, bytes); + } + + setLocalFileTimestamp(f, tf.updated); + + runningRequest.set(true); + + }); uploadTask.execute(); await(); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBAEventFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBAEventFragment.java index 3ab7c66..9e7b121 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBAEventFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBAEventFragment.java @@ -8,7 +8,11 @@ import static com.ridgebotics.ridgescout.utility.FileEditor.TBAAddress; import static com.ridgebotics.ridgescout.utility.FileEditor.TBAHeader; import android.app.ProgressDialog; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.Bundle; +import android.util.Base64; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -22,6 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.google.android.material.button.MaterialButton; import com.ridgebotics.ridgescout.R; import com.ridgebotics.ridgescout.databinding.FragmentTransferTbaBinding; import com.ridgebotics.ridgescout.types.frcEvent; @@ -33,6 +38,7 @@ import com.ridgebotics.ridgescout.utility.JSONUtil; import com.ridgebotics.ridgescout.utility.RequestTask; import com.ridgebotics.ridgescout.utility.FileEditor; import com.ridgebotics.ridgescout.utility.SettingsManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import org.json.JSONArray; import org.json.JSONException; @@ -40,6 +46,7 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.Function; // Class to download data from a specific event and encode it. public class TBAEventFragment extends Fragment { @@ -49,8 +56,6 @@ public class TBAEventFragment extends Fragment { private final int year = SettingsManager.getYearNum(); - private ProgressDialog loadingDialog; - private static JSONObject eventData = null; public static void setEventData(JSONObject j){ eventData = j; @@ -72,27 +77,18 @@ public class TBAEventFragment extends Fragment { Table = binding.matchTable; - Table.setStretchAllColumns(true); + AlertManager.startLoading("Loading Teams and Matches..."); - startLoading("Loading Teams and Matches..."); - Table.removeAllViews(); +// Table.removeAllViews(); Table.setStretchAllColumns(true); Table.bringToFront(); - TableRow tr1 = new TableRow(getContext()); - addTableText(tr1, "Downloading Teams..."); - Table.addView(tr1); - final RequestTask rq = new RequestTask(); rq.onResult(teamsStr -> { - TableRow tr11 = new TableRow(getContext()); - addTableText(tr11, "Downloading Matches..."); - Table.addView(tr11); - final RequestTask rq1 = new RequestTask(); rq1.onResult(matchesStr -> { matchTable(matchesStr, teamsStr, eventData); - stopLoading(); + AlertManager.stopLoading(); return null; }); rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader); @@ -104,18 +100,13 @@ public class TBAEventFragment extends Fragment { } private void addTableText(TableRow tr, String textStr){ - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center - text.setText(textStr); - tr.addView(text); + tr.addView(new TextViewBuilder(getContext(), textStr) + .size(18) +// .align_center() + .build()); } public void matchTable(String matchesString, String teamsString, JSONObject eventData){ - Table.removeAllViews(); - Table.setStretchAllColumns(true); - Table.bringToFront(); - try { final JSONArray matchData = new JSONArray(matchesString); // final JSONArray matchData = new JSONArray(); @@ -130,30 +121,19 @@ public class TBAEventFragment extends Fragment { } // Event code at top - TextView tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setText(matchKey); - tv.setTextSize(18); - Table.addView(tv); + Table.addView(new TextViewBuilder(getContext(), matchKey) + .align_center() + .size(18) + .build()); // Event Name - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText(matchName); - tv.setTextSize(28); - Table.addView(tv); - - + Table.addView(new TextViewBuilder(getContext(), matchName) + .align_center() + .size(28) + .build()); // Save button - Button btn = new Button(getContext()); + MaterialButton btn = new MaterialButton(getContext()); btn.setText("Save"); btn.setTextSize(18); btn.setLayoutParams(new TableRow.LayoutParams( @@ -165,68 +145,42 @@ public class TBAEventFragment extends Fragment { + // If there are no matches, add the error. + // If there are no teams, don't allow the user to save the event and set the button to be invisible if(teamData.length() == 0){ - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("This event has no teams released yet..."); - tv.setTextSize(18); - Table.addView(tv); - - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("This event has no teams released yet..."); - tv.setTextSize(18); - Table.addView(tv); + Table.addView(new TextViewBuilder(getContext(), "This event has no teams released yet...") + .align_center() + .size(18) + .build()); btn.setVisibility(View.GONE); return; }else if(matchData.length() == 0){ - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("This event has no matches released yet..."); - tv.setTextSize(18); - Table.addView(tv); + Table.addView(new TextViewBuilder(getContext(), "This event has no matches released yet...") + .align_center() + .size(18) + .build()); - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Try manually adding practice matches."); - tv.setTextSize(18); - Table.addView(tv); + Table.addView(new TextViewBuilder(getContext(), "Try manually adding practice matches.") + .align_center() + .size(18) + .build()); } - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Teams"); - tv.setTextSize(28); - Table.addView(tv); - + Table.addView( + new TextViewBuilder(getContext(), "Teams") + .align_center() + .size(28) + .build() + ); + // Sort the teams into numerical order int[] teams = new int[teamData.length()]; for(int i = 0 ; i < teamData.length(); i++){ @@ -235,28 +189,26 @@ public class TBAEventFragment extends Fragment { Arrays.sort(teams); + + // Loop through each match TableRow tr = null; for(int i=0; i < teamData.length(); i++){ -// frcTeam team = event.teams.get(i); int num = teams[i]; + // If this is every 7th row, add the new row. if(i % 7 == 0){ if(i != 0) Table.addView(tr); tr = new TableRow(getContext()); } - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - text.setText(String.valueOf(num)); -// if(fileEditor.fileExist(event.eventCode + "-" + num + ".pitscoutdata")){ -// text.setBackgroundColor(0x3000FF00); -// }else{ -// text.setBackgroundColor(0x30FF0000); -// } - tr.addView(text); + tr.addView( + new TextViewBuilder(getContext(), String.valueOf(num)) + .align_center() + .size(18) + .build() + ); } if(tr != null) Table.addView(tr); @@ -269,19 +221,13 @@ public class TBAEventFragment extends Fragment { - tv = new TextView(getContext()); - tv.setLayoutParams(new TableRow.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - tv.setText("Matches"); - tv.setTextSize(28); - Table.addView(tv); - - - - + Table.addView( + new TextViewBuilder(getContext(), "Matches") + .align_center() + .size(28) + .build() + ); + tr = new TableRow(getContext()); addTableText(tr, "#"); addTableText(tr, "Red-1"); @@ -335,22 +281,24 @@ public class TBAEventFragment extends Fragment { int[] redKeys = new int[3]; for(int b=0;b<6;b++){ - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center - tr.addView(text); + TextViewBuilder text = new TextViewBuilder(getContext()) + .size(18) + .align_center(); if(b < 3){ String str = redAlliance.getString(b).substring(3); redKeys[b] = Integer.parseInt(str); - text.setText(str); - text.setBackgroundColor(tba_red); + text.text(str); + text.tv.setBackgroundColor(tba_red); }else{ String str = blueAlliance.getString(b-3).substring(3); blueKeys[b-3] = Integer.parseInt(str); - text.setText(str); - text.setBackgroundColor(tba_blue); + text.text(str); + text.tv.setBackgroundColor(tba_blue); } + + + tr.addView(text.build()); } Table.addView(tr); @@ -365,22 +313,14 @@ public class TBAEventFragment extends Fragment { toggle = !toggle; } -// btn.setOnClickListener(v -> { -// if(saveData(matchesOBJ, teamData, eventData)){ -// alert("Info", "Saved!"); -// }else{ -// alert("Error", "Error saving files."); -// } -// }); - }catch (JSONException j){ AlertManager.error("Failed Downloading", j); - stopLoading(); + AlertManager.stopLoading(); } } private boolean saveData(ArrayList matchData, JSONArray teamData, JSONObject eventData){ - startLoading("Saving data..."); + AlertManager.startLoading("Downloading team data..."); Thread t = new Thread(() -> { try { @@ -405,16 +345,44 @@ public class TBAEventFragment extends Fragment { teamObj.country = team.getString("country"); teamObj.startingYear = team.getInt("rookie_year"); - ImageRequestTask imageRequestTask = new ImageRequestTask(); - imageRequestTask.onResult(bitmap -> { - teamObj.bitmap = bitmap; - teamObj.teamColor = frcTeam.findPrimaryColor(bitmap); - teams.add(teamObj); + RequestTask rq = new RequestTask(); + rq.onResult(s -> { + try { + JSONArray jsonArray = new JSONArray(s); + JSONObject jsonObject = jsonArray.getJSONObject(0); + String base64 = jsonObject.getJSONObject("details").getString("base64Image"); + byte[] decodedData = Base64.decode(base64, Base64.DEFAULT); + Bitmap bitmap = BitmapFactory.decodeByteArray(decodedData, 0, decodedData.length); + +// System.out.println(base64); + + teamObj.bitmap = bitmap; + teamObj.teamColor = frcTeam.findPrimaryColor(bitmap); + + Log.i("TBA", "Got icon for team " + teamObj.teamNumber); + + + } catch (Exception e){ + Log.i("TBA", "Failed to icon for team " + teamObj.teamNumber); + } finally { + teams.add(teamObj); + } return null; }); - imageRequestTask.execute("https://www.thebluealliance.com/avatar/" + year + "/frc" + teamObj.teamNumber + ".png"); + rq.execute((TBAAddress + "team/frc" + teamObj.teamNumber + "/media/" + year), TBAHeader); + +// ImageRequestTask imageRequestTask = new ImageRequestTask(); +// +// imageRequestTask.onResult(bitmap -> { +// teamObj.bitmap = bitmap; +// teamObj.teamColor = frcTeam.findPrimaryColor(bitmap); +// teams.add(teamObj); +// +// return null; +// }); +// imageRequestTask.execute("https://www.thebluealliance.com/avatar/" + year + "/frc" + teamObj.teamNumber + ".png"); } while (teams.size() != teamData.length()) { @@ -431,31 +399,15 @@ public class TBAEventFragment extends Fragment { AlertManager.toast("Saved!"); getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer)); - stopLoading(); + AlertManager.stopLoading(); }catch(Exception j) { AlertManager.error(j); - stopLoading(); + AlertManager.stopLoading(); } }); t.start(); return false; } - - private void startLoading(String title){ - getActivity().runOnUiThread(() -> { - if(loadingDialog != null && loadingDialog.isShowing()) - loadingDialog.dismiss(); - loadingDialog = ProgressDialog.show(getActivity(), title, "Please wait..."); - }); - } - - private void stopLoading(){ - getActivity().runOnUiThread(() -> { - if (loadingDialog != null) - loadingDialog.cancel(); - loadingDialog = null; - }); - } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBASelectorFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBASelectorFragment.java index 6bed0ba..15c5918 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBASelectorFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TBASelectorFragment.java @@ -25,6 +25,7 @@ import com.ridgebotics.ridgescout.ui.views.TBAEventOption; import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.RequestTask; import com.ridgebotics.ridgescout.utility.SettingsManager; +import com.ridgebotics.ridgescout.utility.builders.TextViewBuilder; import org.json.JSONArray; import org.json.JSONException; @@ -54,12 +55,6 @@ public class TBASelectorFragment extends Fragment { Table = binding.matchTable; - Table.setStretchAllColumns(true); - - TableRow tr = new TableRow(getContext()); - addTableText(tr, "Loading Events..."); - Table.addView(tr); - startLoading("Loading Events..."); final RequestTask rq = new RequestTask(); @@ -77,14 +72,6 @@ public class TBASelectorFragment extends Fragment { return binding.getRoot(); } - private void addTableText(TableRow tr, String textStr){ - TextView text = new TextView(getContext()); - text.setTextSize(18); - text.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); // Text align center - text.setText(textStr); - tr.addView(text); - } - public static int getEventTypeWeight(String type){ switch(type){ case "Preseason": return -3; @@ -103,7 +90,7 @@ public class TBASelectorFragment extends Fragment { public void eventTable(String dataString){ Table.removeAllViews(); - Table.setStretchAllColumns(true); +// Table.setStretchAllColumns(true); Table.bringToFront(); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGenTask.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGenTask.java new file mode 100644 index 0000000..0d2ac65 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGenTask.java @@ -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> { +// private Function, String> resultFunction = null; +// +// @Override +// protected List doInBackground(String... strings) { +// + +// +// return new ArrayList<>(); +// } +// +// +// public void onResult(Function, String> func) { +// this.resultFunction = func; +// } +// +// +// @Override +// protected void onPostExecute(List 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 call() { + List 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 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; + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGeneratorView.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGeneratorView.java index 35d9302..d0b88d4 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGeneratorView.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeGeneratorView.java @@ -1,9 +1,10 @@ package com.ridgebotics.ridgescout.ui.transfer.codes; import android.graphics.Bitmap; -import android.graphics.Color; import android.os.Bundle; import android.os.CountDownTimer; +import android.os.Handler; +import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -18,16 +19,11 @@ import androidx.fragment.app.Fragment; import com.ridgebotics.ridgescout.databinding.FragmentTransferCodeSenderBinding; import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.FileEditor; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.MultiFormatWriter; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; +import com.ridgebotics.ridgescout.utility.TaskRunner; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.EnumMap; -import java.util.Map; +import java.util.List; import java.util.Random; // Class to show the code transfer thing. @@ -38,23 +34,28 @@ public class CodeGeneratorView extends Fragment { private TextView qrIndexN; private TextView qrIndexD; - private final int maxQrCount = 256; //The max number that can be stored in a byte + private static final int maxQrCount = 256; //The max number that can be stored in a byte - private final int maxQrSpeed = 5; - private final int minQrSpeed = 300 + maxQrSpeed - 1; + private static final int maxQrSize = 800; + + + private static final int maxQrSpeed = 50; + private static final int minQrSpeed = 1000; + private static final int defaultQrDelay = 12; + + + private int imageSize; private int minQrSize = 0; - private final int maxQrSize = 800; private int qrSize = 200; - private final int defaultQrDelay = 419; - private int qrDelay = 0; + private double qrDelay = 0; private int qrIndex = 0; private CountDownTimer timer; private int qrCount = 0; - private ArrayList qrBitmaps; + private List qrBitmaps = new ArrayList<>(); private FragmentTransferCodeSenderBinding binding; @@ -77,6 +78,12 @@ public class CodeGeneratorView extends Fragment { qrIndexN = binding.qrIndexN; qrIndexD = binding.qrIndexD; + DisplayMetrics displaymetrics = new DisplayMetrics(); + getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); +// int height = displaymetrics.heightPixels; + imageSize = displaymetrics.widthPixels; +// = 800; + String compressed = new String(FileEditor.blockCompress(data, FileEditor.lengthHeaderBytes), StandardCharsets.ISO_8859_1); if(compressed.isEmpty()){ @@ -85,75 +92,30 @@ public class CodeGeneratorView extends Fragment { } minQrSize = Math.round((float)compressed.length() / maxQrCount)+1; - - qrSizeSlider.setMax(maxQrSize-minQrSize); - qrSpeedSlider.setMax((minQrSpeed-maxQrSpeed)*2); - - qrSizeSlider.setProgress(minQrSize+qrSize); - qrSpeedSlider.setProgress(defaultQrDelay+5); + qrSize += minQrSize; sendData(compressed); + qrSpeedSlider.setMax(maxQrSpeed*2); + qrSpeedSlider.setProgress(maxQrSpeed + defaultQrDelay); + + qrSizeSlider.setMax(maxQrSize-minQrSize); + qrSizeSlider.setProgress(qrSize-minQrSize); + + startLoop(); + return binding.getRoot(); } - - private Bitmap generateQrCode(String contents) throws WriterException { - - final int size = 512; - - if (contents == null) { - return null; - } - - Map hints = new EnumMap<>(EncodeHintType.class); - - // The Charset must be UTF-8, Or data will not be transferred properly. IDK why. - hints.put(EncodeHintType.CHARACTER_SET, "ISO-8859-1"); -// hints.put(EncodeHintType.); - hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */ - MultiFormatWriter writer = new MultiFormatWriter(); - - BitMatrix result; - try { - result = writer.encode(contents, BarcodeFormat.DATA_MATRIX, size, size, hints); - } catch (IllegalArgumentException e) { - // Unsupported format - AlertManager.error(e); - return null; - } - - int width = result.getWidth(); - int height = result.getHeight(); - int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) { - int offset = y * width; - for (int x = 0; x < width; x++) { - pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE; - } - } - - Bitmap bitmap = Bitmap.createBitmap(width, height, - Bitmap.Config.ARGB_8888); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - - return bitmap; - } - - private void sendData(String data){ - - - qrCount = (data.length()/qrSize)+1; qrIndexD.setText(String.valueOf(qrCount)); -// alert("size", ""+binding.qrSizeSlider.getProgress()+"\n"+binding.qrSizeSlider.getMax()); - qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - qrDelay = -(minQrSpeed - progress - maxQrSpeed + 1); + qrDelay = ((double) progress /maxQrSpeed) - 1; +// startLoop(); } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @@ -173,43 +135,24 @@ public class CodeGeneratorView extends Fragment { qrCount = ((data.length()+1)/qrSize) +1; qrIndexD.setText(String.valueOf(qrCount)); sendData(data); +// startLoop(); } }); -// qrSizeSlider.setProgress(qr); + AlertManager.startLoading("Generating codes..."); - qrBitmaps = new ArrayList<>(); + new TaskRunner().executeAsync(new CodeGenTask(data, new Random().nextInt(255), qrSize, qrCount, imageSize), result -> { + qrBitmaps = result; + AlertManager.stopLoading(); + qrIndex = 0; + }); - int randID = new Random().nextInt(255); - - for(int i=0;i<=((data.length()+1)/qrSize);i++){ - final int start = i*qrSize; - int end = (i+1)*qrSize; - if(end >= data.length()){ - end = data.length(); - } - try { -// alert("test", ""+Math.ceil((double)data.length()/(double)qrSize)); - qrBitmaps.add(generateQrCode( - FileEditor.byteToChar(FileEditor.internalDataVersion, FileEditor.lengthHeaderBytes) + - String.valueOf(FileEditor.byteToChar(randID, FileEditor.lengthHeaderBytes)) + - FileEditor.byteToChar(i, FileEditor.lengthHeaderBytes) + - FileEditor.byteToChar(qrCount - 1, FileEditor.lengthHeaderBytes) + - data.substring(start, end) - )); -// alert("title", ""+(qrCount-1)); - }catch (WriterException e){ - AlertManager.error(e); - } - } - qrIndex = 0; - if(timer != null){ - timer.cancel(); - } - qrLoop(); } private void updateQr(){ + if(qrBitmaps.isEmpty()) + return; + qrImage.setImageBitmap(qrBitmaps.get(qrIndex)); if(qrDelay > 0) { this.qrIndex += 1; @@ -226,13 +169,36 @@ public class CodeGeneratorView extends Fragment { qrIndexN.setText(String.valueOf(qrIndex+1)); } - private void qrLoop(){ - timer = new CountDownTimer(minQrSpeed-Math.abs(qrDelay)+1, 1000) { - public void onTick(long millisUntilFinished) {} - public void onFinish() { - updateQr(); - qrLoop(); + private boolean shouldstop = false; + + private void startLoop() { + final Handler handler = new Handler(); + Runnable runnable = new Runnable() { + + @Override + public void run() { + if(shouldstop){ + return; + } + try{ + updateQr(); + } + catch (Exception e) { + AlertManager.error(e); + } + finally{ + double a = ((double) maxQrSpeed) / (Math.abs(qrDelay)); + a = Math.min(Math.max(a, maxQrSpeed), minQrSpeed); + handler.postDelayed(this, (long) a); + } } - }.start(); + }; + handler.post(runnable); + } + + @Override + public void onDestroy() { + super.onDestroy(); + shouldstop = true; } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeOverlayView.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeOverlayView.java index ee3c2ea..7948db3 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeOverlayView.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeOverlayView.java @@ -65,7 +65,7 @@ public class CodeOverlayView extends View { } } if(barColors != null){ - final double width = getWidth()/barColors.length; + final double width = (double) getWidth() /barColors.length; final int top = 0; final int bottom = barHeight; diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScanTask.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScanTask.java index b48d6ef..13e547f 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScanTask.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScanTask.java @@ -14,19 +14,69 @@ import com.google.zxing.Reader; import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.datamatrix.DataMatrixReader; +import com.ridgebotics.ridgescout.utility.TaskRunner; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; import java.util.function.Function; // Background task for code scanning, to not slow down the scanner. -public class CodeScanTask extends AsyncTask{ - private Function resultFunction = null; +//public class CodeScanTask extends AsyncTask{ +// private Function 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 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 func) { +// this.resultFunction = func; +// } +// +// @Override +// protected void onPostExecute(String result) { +// super.onPostExecute(result); +// if(resultFunction != null){ +// resultFunction.apply(result); +// } +// } +//} + +public class CodeScanTask implements Callable { private Bitmap image; + public CodeScanTask(Bitmap image) { + this.image = image; + } + @Override - protected String doInBackground(String... str) { + public String call() { if(image == null){return null;} int width = image.getWidth(); @@ -52,17 +102,4 @@ public class CodeScanTask extends AsyncTask{ return null; } - public void setImage(Bitmap image){this.image = image;} - public void onResult(Function func) { - this.resultFunction = func; - } - - @Override - protected void onPostExecute(String result) { - super.onPostExecute(result); - if(resultFunction != null){ - resultFunction.apply(result); - } - } -} - +} \ No newline at end of file diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScannerView.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScannerView.java index 5be67a3..f1128ed 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScannerView.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/codes/CodeScannerView.java @@ -6,6 +6,7 @@ import android.Manifest; import android.app.AlertDialog; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.Matrix; import android.media.Image; import android.os.Bundle; import android.os.Handler; @@ -38,11 +39,15 @@ import com.ridgebotics.ridgescout.utility.AlertManager; import com.ridgebotics.ridgescout.utility.BuiltByteParser; import com.ridgebotics.ridgescout.utility.FileEditor; import com.google.common.util.concurrent.ListenableFuture; +import com.ridgebotics.ridgescout.utility.TaskRunner; + +import org.checkerframework.checker.units.qual.C; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -62,21 +67,12 @@ public class CodeScannerView extends Fragment { alert.create().show(); } - - private float scale = 0; private final int downscale = 1; private LifecycleOwner lifecycle; private void setImage(Bitmap bmp){ - if(scale == 0) { - scale = ((float) binding.container.getWidth() / bmp.getWidth()) * ((float) 16 / 9); - binding.scannerImage.setTranslationX(0); - binding.scannerImage.setTranslationY(0); - } scanQRCode(bmp); binding.scannerImage.setImageBitmap(bmp); - binding.scannerThreshold.bringToFront(); -// alert("test", getChildCount()+""); } // private Bitmap img @@ -99,37 +95,38 @@ public class CodeScannerView extends Fragment { final int height = image.getHeight(); int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int L = levelMap[yBuffer.get() & 0xff]; - pixels[y * width + x] = 0xff000000 | (L << 16) | (L << 8) | L; - } + for (int i = 0; i < width*height; i++) { + int L = levelMap[yBuffer.get(i) & 0xff]; + pixels[i] = 0xff000000 | (L << 16) | (L << 8) | L; } + Matrix matrix = new Matrix(); + + matrix.postRotate(90); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); +// Bitmap.rota return bitmap; } public void scanQRCode(Bitmap bitmap) { - CodeScanTask async = new CodeScanTask(); - async.setImage(bitmap); - async.onResult(data -> { +// CodeScanTask async = new CodeScanTask(); + new TaskRunner().executeAsync(new CodeScanTask(bitmap), data -> { if(data != null){ // alert("test", ""+fileEditor.byteFromChar(data.charAt(0))); compileData( - FileEditor.byteFromChar(data.charAt(0)), - FileEditor.byteFromChar(data.charAt(1)), - FileEditor.byteFromChar(data.charAt(2)), - (FileEditor.byteFromChar(data.charAt(3))+1), - data.substring(4) + FileEditor.byteFromChar(data.charAt(0)), + FileEditor.byteFromChar(data.charAt(1)), + FileEditor.byteFromChar(data.charAt(2)), + (FileEditor.byteFromChar(data.charAt(3))+1), + data.substring(4) ); } - return null; }); - async.execute(); - // return contents; @@ -231,26 +228,23 @@ public class CodeScannerView extends Fragment { void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { - Preview preview = new Preview.Builder() - .setTargetAspectRatio(AspectRatio.RATIO_16_9) - .setTargetRotation(Surface.ROTATION_180) - .build(); + Preview preview = new Preview.Builder().build(); CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) + // .addCameraFilter(CameraFilters.NON) .build(); ExecutorService executor = Executors.newSingleThreadExecutor(); ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() -// .setTargetResolution(new Size(224, 224)) - .setOutputImageRotationEnabled(false) - .setTargetAspectRatio(AspectRatio.RATIO_16_9) +// .setTargetAspectRatio(AspectRatio.RATIO_16_9) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) +// .setOutputImageRotationEnabled(true) +// .setTargetRotation(Surface.ROTATION_0) .build(); - imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() { @OptIn(markerClass = ExperimentalGetImage.class) @Override public void analyze(@NonNull ImageProxy image) { @@ -269,6 +263,7 @@ public class CodeScannerView extends Fragment { }); cameraProvider.unbindAll(); +// cameraProvider.ro cameraProvider.bindToLifecycle(lifecycle, cameraSelector, imageAnalysis, preview); @@ -295,6 +290,7 @@ public class CodeScannerView extends Fragment { Log.i("title", ""+qrCount); barColors = new int[qrCount]; prevQrIndex = qrIndex; + qrScannedCount = 0; } final boolean updated; @@ -313,8 +309,20 @@ public class CodeScannerView extends Fragment { if(updated && qrScannedCount >= qrCount){ + AlertManager.startLoading("Decoding data..."); + new TaskRunner().executeAsync(new CodeDecodeTask(), result -> { + AlertManager.stopLoading(); + }); + + } + prevQrIndex = qrIndex; + } + + private class CodeDecodeTask implements Callable { + @Override + public Void call() { String compiledString = ""; - for(int i=0;i { // dialog.(); if(!isEnabled()) return; - item.setText(option); + item.setText("▼ " + option); index = options.indexOf(option); if(onClickListener != null) { onClickListener.onClick(option, options.indexOf(option)); @@ -105,12 +105,12 @@ public class CustomSpinnerView extends LinearLayout { } public void setOption(String option) { - item.setText(option); + item.setText("▼ " + option); index = options.indexOf(option); } public void setOption(int index) { - item.setText(options.get(index)); + item.setText("▼ " + options.get(index)); this.index = index; } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/views/MatchScoutingIndicator.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/views/MatchScoutingIndicator.java index 8571d85..28a9fd2 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/views/MatchScoutingIndicator.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/views/MatchScoutingIndicator.java @@ -1,6 +1,7 @@ package com.ridgebotics.ridgescout.ui.views; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; @@ -54,6 +55,20 @@ public class MatchScoutingIndicator extends RelativeLayout { match_indicator_bar_team_num = findViewById(R.id.match_indicator_bar_team_num); box = findViewById(R.id.file_indicator_box); coloredBackground = findViewById(R.id.match_indicator_background); + + int currentNightMode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + switch (currentNightMode) { + case Configuration.UI_MODE_NIGHT_NO: + // Night mode is not active on device + match_indicator_back_button.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + match_indicator_next_button.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + break; + case Configuration.UI_MODE_NIGHT_YES: + // Night mode is active on device + match_indicator_back_button.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); + match_indicator_next_button.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); + break; + } } public void setUsername(String username){ @@ -73,19 +88,31 @@ public class MatchScoutingIndicator extends RelativeLayout { } public void setColor(int color){ - Drawable drawable = box.getBackground(); - drawable.mutate(); - drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + // Set color of main background rectangle + Drawable box_drawable = box.getBackground(); + box_drawable.mutate(); + box_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); float[] hsv = new float[3]; Color.colorToHSV(color,hsv); + int background_color = Color.HSVToColor(220, new float[]{ + hsv[0], + Math.min(hsv[1], 0.75f), + Math.min(hsv[2], 0.5f) + }); + + // Set color of main background rectangle, slightly dimmer coloredBackground.setBackgroundColor( - Color.HSVToColor(220, new float[]{ - hsv[0], - Math.min(hsv[1], 0.75f), - Math.min(hsv[2], 0.5f) - }) + background_color ); + + Drawable left_drawable = match_indicator_back_button.getBackground(); + left_drawable.mutate(); + left_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + + Drawable right_drawable = match_indicator_next_button.getBackground(); + right_drawable.mutate(); + right_drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/views/PitScoutingIndicator.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/views/PitScoutingIndicator.java new file mode 100644 index 0000000..34bed4b --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/views/PitScoutingIndicator.java @@ -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 + ); + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/AlertManager.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/AlertManager.java index 463a295..8094609 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/AlertManager.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/AlertManager.java @@ -3,6 +3,7 @@ package com.ridgebotics.ridgescout.utility; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.widget.Toast; @@ -113,4 +114,22 @@ public class AlertManager { }); } + private static ProgressDialog loadingDialog; + + public static void startLoading(String title){ + ((Activity) context).runOnUiThread(() -> { + if(loadingDialog != null && loadingDialog.isShowing()) + loadingDialog.dismiss(); + loadingDialog = ProgressDialog.show(context, title, "Please wait..."); + }); + } + + public static void stopLoading(){ + ((Activity) context).runOnUiThread(() -> { + if (loadingDialog != null) + loadingDialog.cancel(); + loadingDialog = null; + }); + } + } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/Colors.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/Colors.java index a75682c..a621b9a 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/Colors.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/Colors.java @@ -6,7 +6,7 @@ import android.graphics.Color; public class Colors { // Lists and stuff public static final int color_found = 0x7f00ff00; - public static final int color_rescout = 0x7f0000ff; + public static final int color_rescout = 0xff007fff; public static final int color_not_found = 0x7f7f0000; @@ -14,9 +14,9 @@ public class Colors { public static final int unfocused_background_color = 0x50118811; - public static final int unsaved_color = 0x60ff0000; - public static final int saved_color = 0x6000ff00; - public static final int rescout_color = 0x600000ff; + public static final int unsaved_color = 0xffaa0000; + public static final int saved_color = 0xff00aa00; + public static final int rescout_color = 0xff007fff; // Data graphs diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/DataManager.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/DataManager.java index d4e353f..1575447 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/DataManager.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/DataManager.java @@ -2,13 +2,10 @@ package com.ridgebotics.ridgescout.utility; import com.ridgebotics.ridgescout.scoutingData.Fields; import com.ridgebotics.ridgescout.scoutingData.transfer.TransferType; +import com.ridgebotics.ridgescout.types.ColabArray; import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.input.FieldType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - // Static class to hold loaded data, for ease of access. public class DataManager { public static String evcode; @@ -62,37 +59,39 @@ public class DataManager { } } - public static List rescout_list = new ArrayList<>(); + + + + + public static ColabArray rescout_list = new ColabArray(); public static void reload_rescout_list(){ - if(!FileEditor.fileExist(evcode + ".rescout")) {rescout_list = new ArrayList<>(); return;} - byte[] file = FileEditor.readFile(evcode + ".rescout"); - if(file == null) {rescout_list = new ArrayList<>(); return;} + String filename = evcode + ".rescout"; + if(!FileEditor.fileExist(filename)) {rescout_list = new ColabArray(); return;} + byte[] file = FileEditor.readFile(filename); + if(file == null) {rescout_list = new ColabArray(); return;} try { - BuiltByteParser bbp = new BuiltByteParser(file); - rescout_list = new ArrayList<>(Arrays.asList((String[]) (bbp.parse().get(0).get()))); - + rescout_list = ColabArray.decode(file); } catch (Exception e){ - AlertManager.error("Error loading scout fields", e); - rescout_list = new ArrayList<>(); + AlertManager.error("Error loading rescouting list", e); + rescout_list = new ColabArray(); } } public static void save_rescout_list() { + String filename = evcode + ".rescout"; try { - if(rescout_list.size() == 0){ - FileEditor.deleteFile(evcode + ".rescout"); - return; - } - - ByteBuilder bb = new ByteBuilder(); - bb.addStringArray(rescout_list.toArray(new String[0])); - FileEditor.writeFile(evcode + ".rescout", bb.build()); + FileEditor.writeFile(filename, rescout_list.encode()); } catch (Exception e){ - AlertManager.error("Error saving scout fields", e); + AlertManager.error("Error saving rescouting list", e); } } + + + + + public static String scoutNotice = ""; public static void reload_scout_notice(){ @@ -106,7 +105,7 @@ public class DataManager { } catch (Exception e){ AlertManager.error("Error loading scout notice", e); - rescout_list = new ArrayList<>(); + scoutNotice = ""; } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/FileEditor.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/FileEditor.java index b57d3bb..eef5432 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/FileEditor.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/FileEditor.java @@ -1,7 +1,15 @@ package com.ridgebotics.ridgescout.utility; +import static com.ridgebotics.ridgescout.utility.DataManager.match_transferValues; +import static com.ridgebotics.ridgescout.utility.DataManager.match_values; +import static com.ridgebotics.ridgescout.utility.DataManager.pit_transferValues; +import static com.ridgebotics.ridgescout.utility.DataManager.pit_values; + +import android.annotation.SuppressLint; import android.content.Context; +import com.ridgebotics.ridgescout.scoutingData.ScoutingDataWriter; +import com.ridgebotics.ridgescout.types.ColabArray; import com.ridgebotics.ridgescout.types.frcEvent; import com.ridgebotics.ridgescout.types.frcTeam; @@ -15,6 +23,8 @@ import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,18 +37,17 @@ import java.util.zip.Inflater; // Helper class for binary editing public final class FileEditor { + @SuppressLint("SdCardPath") public final static String baseDir = "/data/data/com.ridgebotics.ridgescout/"; public static final byte internalDataVersion = 0x01; public static final int maxCompressedBlockSize = 4096; public static final int lengthHeaderBytes = 3; - public static final String TBAAddress = "https://www.thebluealliance.com/api/v3/"; + + // Hardcoded API key go brrr public static final String TBAHeader = "X-TBA-Auth-Key: tjEKSZojAU2pgbs2mBt06SKyOakVhLutj3NwuxLTxPKQPLih11aCIwRIVFXKzY4e"; -// private TimeZone localTimeZone = TimeZone.getDefault(); - - public static String binaryVisualize(byte[] bytes){ String returnStr = ""; @@ -100,7 +109,7 @@ public final class FileEditor { public static int byteFromChar(char c){ - byte[] bytes = (String.valueOf(c)).getBytes(Charset.defaultCharset()); + byte[] bytes = (String.valueOf(c)).getBytes(StandardCharsets.ISO_8859_1); return Byte.toUnsignedInt(bytes[0]); } @@ -233,16 +242,19 @@ public final class FileEditor { // } - public static boolean writeFile(String filepath, byte[] data) { + return writeFile(new File(baseDir + filepath), data); + } + + public static boolean writeFile(File file, byte[] data) { try { - FileOutputStream output = new FileOutputStream(baseDir + filepath); + FileOutputStream output = new FileOutputStream(file.getPath()); output.write(data); output.close(); // Date d = new Date(); - new File(baseDir + filepath).setLastModified(new Date().getTime()); + file.setLastModified(new Date().getTime()); return true; } catch (IOException e) { @@ -279,10 +291,15 @@ public final class FileEditor { } public static byte[] readFile(String path){ - return readFileExact(baseDir + path); + return readFileExact(new File(baseDir + path)); } - public static byte[] readFileExact(String path){ - File file = new File(path); + + public static byte[] readFile(File path){ + return readFileExact(path); + } + + + public static byte[] readFileExact(File file){ int size = (int) file.length(); byte[] bytes = new byte[size]; try { @@ -290,9 +307,6 @@ public final class FileEditor { buf.read(bytes, 0, bytes.length); buf.close(); return bytes; - } catch (FileNotFoundException e) { - AlertManager.error(e); - return null; } catch (IOException e) { AlertManager.error(e); return null; @@ -314,6 +328,29 @@ public final class FileEditor { + public static String getSHA256Hash(String filePath) throws IOException, NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + FileInputStream fis = new FileInputStream(baseDir + filePath); + byte[] byteArray = new byte[1024]; + int bytesCount = 0; + + while ((bytesCount = fis.read(byteArray)) != -1) { + digest.update(byteArray, 0, bytesCount); + } + fis.close(); + + byte[] bytes = digest.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + + + + public static boolean setEvent(frcEvent event){ @@ -373,34 +410,31 @@ public final class FileEditor { - - public static String[] getEventFiles(String evcode){ + public static String[] getFiles(){ File f = new File(baseDir); File[] files = f.listFiles(); if(files == null){return new String[0];} - ArrayList outFiles = new ArrayList<>(); - outFiles.add("matches.fields"); - outFiles.add("pits.fields"); -// outFiles.add(evcode + ".eventdata"); + List outFiles = new ArrayList<>(); for (File file : files) { - String name = file.getName(); - if(!file.isDirectory() && name.startsWith(evcode)) { + if (!file.isDirectory()) { outFiles.add(file.getName()); } } + String[] filenames = outFiles.toArray(new String[0]); try { Arrays.sort(filenames, (o1, o2) -> { try { if (!o1.contains("-") || !o2.contains("-")) - return 0; - return Integer.valueOf(o1.split("-")[1]).compareTo(Integer.valueOf(o2.split("-")[1])); + return o2.compareTo(o1); + return Integer.valueOf(o1.split("-")[1].split("\\.")[0]).compareTo(Integer.valueOf(o2.split("-")[1].split("\\.")[0])); } catch (Exception e) { + AlertManager.error(e); return 0; } }); @@ -412,6 +446,24 @@ public final class FileEditor { return filenames; } + + + public static String[] getEventFiles(String evcode){ + String[] files = getFiles(); + + List outFiles = new ArrayList<>(); + outFiles.add("matches.fields"); + outFiles.add("pits.fields"); + + for (String file : files) { + if(file.startsWith(evcode)) { + outFiles.add(file); + } + } + + return outFiles.toArray(new String[0]); + } + // https://stackoverflow.com/questions/7620401/how-to-convert-image-file-data-in-a-byte-array-to-a-bitmap // public static String imageToBitMap(byte[] data) throws IOException { // Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); @@ -424,5 +476,72 @@ public final class FileEditor { public static boolean setTeams(Context context, String key, ArrayList teams){ return true; } + + public static List findCorruptedFiles() { + List 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); + } + } } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpGetFile.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpGetFile.java index bbc8b66..fd7938d 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpGetFile.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpGetFile.java @@ -5,14 +5,16 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +// Class to download remote file. public class HttpGetFile extends AsyncTask { public interface DownloadCallback { - void onResult(String error); + void onResult(ByteArrayOutputStream bytes, String error); } private String downloadUrl; private File destinationFile; + private ByteArrayOutputStream outputStream; private DownloadCallback callback; private String errorMessage; public HttpGetFile(String downloadUrl, File destinationFile, DownloadCallback callback) { @@ -23,9 +25,12 @@ public class HttpGetFile extends AsyncTask { @Override protected File doInBackground(Void... voids) { + return run(); + } + + public File run() { HttpURLConnection connection = null; InputStream inputStream = null; - FileOutputStream outputStream = null; try { URL url = new URL(downloadUrl); @@ -64,7 +69,7 @@ public class HttpGetFile extends AsyncTask { } } - outputStream = new FileOutputStream(destinationFile); + outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; long downloadedBytes = 0; @@ -87,6 +92,8 @@ public class HttpGetFile extends AsyncTask { } outputStream.flush(); + +// FileEditor.writeFile(destinationFile, outputStream.toByteArray()); // Log.d(TAG, "Download successful. File saved to: " + destinationFile.getAbsolutePath()); return destinationFile; @@ -104,7 +111,7 @@ public class HttpGetFile extends AsyncTask { @Override protected void onPostExecute(File result) { if (callback != null) { - callback.onResult(errorMessage); + callback.onResult(outputStream, errorMessage); } } @@ -112,7 +119,7 @@ public class HttpGetFile extends AsyncTask { protected void onCancelled() { deletePartialFile(); if (callback != null) { - callback.onResult("Download cancelled"); + callback.onResult(null, "Download cancelled"); } } @@ -130,8 +137,7 @@ public class HttpGetFile extends AsyncTask { return response.toString(); } } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error reading error response", e); + AlertManager.error("Error reading error response", e); } return null; } @@ -139,9 +145,9 @@ public class HttpGetFile extends AsyncTask { private void deletePartialFile() { if (destinationFile != null && destinationFile.exists()) { if (destinationFile.delete()) { -// Log.d(TAG, "Partial download file deleted"); + AlertManager.error("Partial download file deleted"); } else { -// Log.w(TAG, "Failed to delete partial download file"); + AlertManager.error("Failed to delete partial download file"); } } } @@ -150,15 +156,13 @@ public class HttpGetFile extends AsyncTask { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error closing input stream", e); + AlertManager.error("Error closing input stream", e); } try { if (outputStream != null) outputStream.close(); } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error closing output stream", e); + AlertManager.error("Error closing output stream", e); } if (connection != null) { diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpPutFile.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpPutFile.java index 73b3583..87407ff 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpPutFile.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/HttpPutFile.java @@ -1,11 +1,14 @@ package com.ridgebotics.ridgescout.utility; +import android.annotation.SuppressLint; import android.os.AsyncTask; //import android.util.Log; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.util.concurrent.atomic.AtomicReference; +// Class to send HTTP PUT request to upload file public class HttpPutFile extends AsyncTask { // private static final String TAG = "FileUploadTask"; @@ -27,8 +30,13 @@ public class HttpPutFile extends AsyncTask { this.headers = headers; } + @SuppressLint("WrongThread") @Override protected Boolean doInBackground(Void... voids) { + return run(); + } + + public boolean run() { HttpURLConnection connection = null; InputStream fileInputStream = null; OutputStream outputStream = null; @@ -49,8 +57,8 @@ public class HttpPutFile extends AsyncTask { connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "application/octet-stream"); connection.setRequestProperty("Content-Length", String.valueOf(fileToUpload.length())); - connection.setConnectTimeout(30000); // 30 seconds - connection.setReadTimeout(60000); // 60 seconds + connection.setConnectTimeout(5000); // 5 seconds + connection.setReadTimeout(10000); // 10 seconds for(int i = 0; i < headers.length; i++){ String[] split = headers[i].split(": "); @@ -95,8 +103,8 @@ public class HttpPutFile extends AsyncTask { } } catch (Exception e) { - AlertManager.error(e); errorMessage = "Upload error: " + e.getMessage(); + AlertManager.error(errorMessage, e); // Log.e(TAG, errorMessage, e); return false; } finally { @@ -133,25 +141,23 @@ public class HttpPutFile extends AsyncTask { return response.toString(); } } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error reading error response", e); + AlertManager.error("Error reading error response", e); } return null; } + // Clean up stream private void closeResources(InputStream inputStream, OutputStream outputStream, HttpURLConnection connection) { try { if (inputStream != null) inputStream.close(); } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error closing input stream", e); + AlertManager.error("Error closing input stream", e); } try { if (outputStream != null) outputStream.close(); } catch (IOException e) { - AlertManager.error(e); -// Log.e(TAG, "Error closing output stream", e); + AlertManager.error("Error closing output stream", e); } if (connection != null) { diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/ImageRequestTask.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/ImageRequestTask.java index 485debb..4bc54f8 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/ImageRequestTask.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/ImageRequestTask.java @@ -25,11 +25,24 @@ public class ImageRequestTask extends AsyncTask { try { URL url = new URL(src); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + // We do a little bit of spoofing + connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"); connection.setDoInput(true); connection.connect(); - InputStream input = connection.getInputStream(); - return BitmapFactory.decodeStream(input); - } catch (FileNotFoundException e) { + + int code = connection.getResponseCode(); + switch (code) { + case 200: + InputStream input = connection.getInputStream(); + return BitmapFactory.decodeStream(input); + case 403: +// AlertManager.error("Got 403, Going to https://www.thebluealliance.com/avatars may fix this"); + return null; + default: + AlertManager.error("Error downloading image " + src, "Got response code: " + code); + return null; + } + } catch (FileNotFoundException e){ return null; } catch (IOException e){ AlertManager.error("Error downloading image " + src, e); diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/TaskRunner.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/TaskRunner.java new file mode 100644 index 0000000..9e95a38 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/TaskRunner.java @@ -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 { + void onComplete(R result); + } + + public void executeAsync(Callable callable, Callback callback) { + executor.execute(() -> { + final R result; + try { + result = callable.call(); + handler.post(() -> { + callback.onComplete(result); + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/ToDelete.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/ToDelete.java new file mode 100644 index 0000000..7e68437 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/ToDelete.java @@ -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 filenames = FileEditor.findCorruptedFiles(); + AlertManager.stopLoading(); + ((Activity) c).runOnUiThread(() -> { + deleteFiles(c, filenames, true); + }); + }).start(); + } + + public static void deleteFiles(Context c, List 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 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 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 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); + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/builders/TextViewBuilder.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/builders/TextViewBuilder.java new file mode 100644 index 0000000..cf18e75 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/builders/TextViewBuilder.java @@ -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; + } +} diff --git a/app/src/main/res/layout/fragment_data_teams.xml b/app/src/main/res/layout/fragment_data_teams.xml index 31518b6..44650d0 100644 --- a/app/src/main/res/layout/fragment_data_teams.xml +++ b/app/src/main/res/layout/fragment_data_teams.xml @@ -29,7 +29,31 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" - tools:layout_editor_absoluteX="0dp" /> + tools:layout_editor_absoluteX="0dp" > + + + + + +