Fix code scanning sliders and remove asynctask

This commit is contained in:
Michael Mikovsky
2025-07-27 12:57:35 -06:00
parent 4aa31b620d
commit dbba56e649
8 changed files with 246 additions and 144 deletions
+1 -18
View File
@@ -33,21 +33,4 @@ https://www.thebluealliance.com/avatars
|Match scouting interface|Field editor|Teams data viewer| |Match scouting interface|Field editor|Teams data viewer|
|-|-|-| |-|-|-|
|![Screenshot1](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/1.png?raw=true)|![Screenshot2](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/2.png?raw=true)|![Screenshot3](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/3.png?raw=true)| |![Screenshot1](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/1.png?raw=true)|![Screenshot2](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/2.png?raw=true)|![Screenshot3](https://github.com/Team4388/ScoutingApp2025/blob/main/metadata/en-US/images/phoneScreenshots/3.png?raw=true)|
z
<!--
Things:
- Added verbosity on could not find team number error
- Tally counter now has more visible font (@style/TextAppearance.MaterialComponents.Headline6)
- Match scouting title bar's background now fills its container (There was a small gap before)
- Added border around match title bar buttons
- Changed the rescout color to be a less obnoxious blue
- Removed blank space above pit scouting team selector
- Added new scouting indicator for pit scouting
- TBA now sometimes does not let you download all of the team images, go to https://www.thebluealliance.com/avatars in a web browser to try to fix this
- Moved dropdown title text box slightly farther up
- Added downwards pointing triangle to dropdown
- Removed slight gap in icon of the team option
- Add headers to settings
- Moved field edit buttons into the scroll view, so there is no overlap
-->
@@ -49,8 +49,6 @@ public class TBAEventFragment extends Fragment {
private final int year = SettingsManager.getYearNum(); private final int year = SettingsManager.getYearNum();
private ProgressDialog loadingDialog;
private static JSONObject eventData = null; private static JSONObject eventData = null;
public static void setEventData(JSONObject j){ public static void setEventData(JSONObject j){
eventData = j; eventData = j;
@@ -74,7 +72,7 @@ public class TBAEventFragment extends Fragment {
Table.setStretchAllColumns(true); Table.setStretchAllColumns(true);
startLoading("Loading Teams and Matches..."); AlertManager.startLoading("Loading Teams and Matches...");
Table.removeAllViews(); Table.removeAllViews();
Table.setStretchAllColumns(true); Table.setStretchAllColumns(true);
Table.bringToFront(); Table.bringToFront();
@@ -92,7 +90,7 @@ public class TBAEventFragment extends Fragment {
final RequestTask rq1 = new RequestTask(); final RequestTask rq1 = new RequestTask();
rq1.onResult(matchesStr -> { rq1.onResult(matchesStr -> {
matchTable(matchesStr, teamsStr, eventData); matchTable(matchesStr, teamsStr, eventData);
stopLoading(); AlertManager.stopLoading();
return null; return null;
}); });
rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader); rq1.execute((TBAAddress + "event/" + matchKey + "/matches"), TBAHeader);
@@ -375,12 +373,12 @@ public class TBAEventFragment extends Fragment {
}catch (JSONException j){ }catch (JSONException j){
AlertManager.error("Failed Downloading", j); AlertManager.error("Failed Downloading", j);
stopLoading(); AlertManager.stopLoading();
} }
} }
private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){ private boolean saveData(ArrayList<frcMatch> matchData, JSONArray teamData, JSONObject eventData){
startLoading("Saving data..."); AlertManager.startLoading("Saving data...");
Thread t = new Thread(() -> { Thread t = new Thread(() -> {
try { try {
@@ -431,31 +429,15 @@ public class TBAEventFragment extends Fragment {
AlertManager.toast("Saved!"); AlertManager.toast("Saved!");
getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer)); getActivity().runOnUiThread(() -> findNavController(this).navigate(R.id.action_navigation_tba_event_to_navigation_transfer));
stopLoading(); AlertManager.stopLoading();
}catch(Exception j) { }catch(Exception j) {
AlertManager.error(j); AlertManager.error(j);
stopLoading(); AlertManager.stopLoading();
} }
}); });
t.start(); t.start();
return false; return false;
} }
private void startLoading(String title){
getActivity().runOnUiThread(() -> {
if(loadingDialog != null && loadingDialog.isShowing())
loadingDialog.dismiss();
loadingDialog = ProgressDialog.show(getActivity(), title, "Please wait...");
});
}
private void stopLoading(){
getActivity().runOnUiThread(() -> {
if (loadingDialog != null)
loadingDialog.cancel();
loadingDialog = null;
});
}
} }
@@ -0,0 +1,129 @@
package com.ridgebotics.ridgescout.ui.transfer.codes;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.ridgebotics.ridgescout.utility.AlertManager;
import com.ridgebotics.ridgescout.utility.FileEditor;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
public class CodeGenTask implements Callable<List<Bitmap>> {
// private Function<List<Bitmap>, String> resultFunction = null;
//
// @Override
// protected List<Bitmap> doInBackground(String... strings) {
//
//
// return new ArrayList<>();
// }
//
//
// public void onResult(Function<List<Bitmap>, String> func) {
// this.resultFunction = func;
// }
//
//
// @Override
// protected void onPostExecute(List<Bitmap> result) {
// super.onPostExecute(result);
// if(resultFunction != null){
// resultFunction.apply(result);
// }
// }
private final String data;
private final int randID;
private final int qrSize;
private final int qrCount;
public CodeGenTask(String data, int randID, int qrSize, int qrCount) {
this.data = data;
this.randID = randID;
this.qrSize = qrSize;
this.qrCount = qrCount;
}
@Override
public List<Bitmap> call() {
List<Bitmap> qrBitmaps = new ArrayList<>();
for(int i=0;i<=((data.length()+1)/qrSize);i++){
final int start = i*qrSize;
int end = (i+1)*qrSize;
if(end >= data.length()){
end = data.length();
}
try {
// 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);
}
}
return qrBitmaps;
}
private Bitmap generateQrCode(String contents) throws WriterException {
final int size = 512;
if (contents == null) {
return null;
}
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
// The Charset must be UTF-8, Or data will not be transferred properly. IDK why.
hints.put(EncodeHintType.CHARACTER_SET, "ISO-8859-1");
// hints.put(EncodeHintType.);
hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix result;
try {
result = writer.encode(contents, BarcodeFormat.DATA_MATRIX, size, size, hints);
} catch (IllegalArgumentException e) {
// Unsupported format
AlertManager.error(e);
return null;
}
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
}
@@ -4,6 +4,7 @@ import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -23,10 +24,12 @@ import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter; import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException; import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.ridgebotics.ridgescout.utility.TaskRunner;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@@ -38,23 +41,27 @@ public class CodeGeneratorView extends Fragment {
private TextView qrIndexN; private TextView qrIndexN;
private TextView qrIndexD; private TextView qrIndexD;
private final int maxQrCount = 256; //The max number that can be stored in a byte private static final int maxQrCount = 256; //The max number that can be stored in a byte
private static final int maxQrSize = 800;
private static final int maxQrSpeed = 50;
private static final int minQrSpeed = 1000;
private static final int defaultQrDelay = 12;
private final int maxQrSpeed = 5;
private final int minQrSpeed = 300 + maxQrSpeed - 1;
private int minQrSize = 0; private int minQrSize = 0;
private final int maxQrSize = 800;
private int qrSize = 200; private int qrSize = 200;
private final int defaultQrDelay = 419; private double qrDelay = 0;
private int qrDelay = 0;
private int qrIndex = 0; private int qrIndex = 0;
private CountDownTimer timer; private CountDownTimer timer;
private int qrCount = 0; private int qrCount = 0;
private ArrayList<Bitmap> qrBitmaps; private List<Bitmap> qrBitmaps = new ArrayList<>();
private FragmentTransferCodeSenderBinding binding; private FragmentTransferCodeSenderBinding binding;
@@ -85,75 +92,29 @@ public class CodeGeneratorView extends Fragment {
} }
minQrSize = Math.round((float)compressed.length() / maxQrCount)+1; minQrSize = Math.round((float)compressed.length() / maxQrCount)+1;
qrSize += minQrSize;
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSpeedSlider.setMax((minQrSpeed-maxQrSpeed)*2);
qrSizeSlider.setProgress(minQrSize+qrSize);
qrSpeedSlider.setProgress(defaultQrDelay+5);
sendData(compressed); sendData(compressed);
qrSpeedSlider.setMax(maxQrSpeed*2);
qrSpeedSlider.setProgress(maxQrSpeed + defaultQrDelay);
qrSizeSlider.setMax(maxQrSize-minQrSize);
qrSizeSlider.setProgress(qrSize-minQrSize);
startLoop();
return binding.getRoot(); return binding.getRoot();
} }
private Bitmap generateQrCode(String contents) throws WriterException {
final int size = 512;
if (contents == null) {
return null;
}
Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
// The Charset must be UTF-8, Or data will not be transferred properly. IDK why.
hints.put(EncodeHintType.CHARACTER_SET, "ISO-8859-1");
// hints.put(EncodeHintType.);
hints.put(EncodeHintType.MARGIN, 0); /* default = 4 */
MultiFormatWriter writer = new MultiFormatWriter();
BitMatrix result;
try {
result = writer.encode(contents, BarcodeFormat.DATA_MATRIX, size, size, hints);
} catch (IllegalArgumentException e) {
// Unsupported format
AlertManager.error(e);
return null;
}
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
private void sendData(String data){ private void sendData(String data){
qrCount = (data.length()/qrSize)+1; qrCount = (data.length()/qrSize)+1;
qrIndexD.setText(String.valueOf(qrCount)); qrIndexD.setText(String.valueOf(qrCount));
// alert("size", ""+binding.qrSizeSlider.getProgress()+"\n"+binding.qrSizeSlider.getMax());
qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { qrSpeedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
qrDelay = -(minQrSpeed - progress - maxQrSpeed + 1); qrDelay = ((double) progress /maxQrSpeed) - 1;
} }
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) {} public void onStartTrackingTouch(SeekBar seekBar) {}
@@ -176,40 +137,20 @@ public class CodeGeneratorView extends Fragment {
} }
}); });
// qrSizeSlider.setProgress(qr); AlertManager.startLoading("Generating codes...");
qrBitmaps = new ArrayList<>(); new TaskRunner().executeAsync(new CodeGenTask(data, new Random().nextInt(255), qrSize, qrCount), result -> {
qrBitmaps = result;
int randID = new Random().nextInt(255); AlertManager.stopLoading();
for(int i=0;i<=((data.length()+1)/qrSize);i++){
final int start = i*qrSize;
int end = (i+1)*qrSize;
if(end >= data.length()){
end = data.length();
}
try {
// alert("test", ""+Math.ceil((double)data.length()/(double)qrSize));
qrBitmaps.add(generateQrCode(
FileEditor.byteToChar(FileEditor.internalDataVersion, FileEditor.lengthHeaderBytes) +
String.valueOf(FileEditor.byteToChar(randID, FileEditor.lengthHeaderBytes)) +
FileEditor.byteToChar(i, FileEditor.lengthHeaderBytes) +
FileEditor.byteToChar(qrCount - 1, FileEditor.lengthHeaderBytes) +
data.substring(start, end)
));
// alert("title", ""+(qrCount-1));
}catch (WriterException e){
AlertManager.error(e);
}
}
qrIndex = 0; qrIndex = 0;
if(timer != null){ });
timer.cancel();
}
qrLoop();
} }
private void updateQr(){ private void updateQr(){
if(qrBitmaps.isEmpty())
return;
qrImage.setImageBitmap(qrBitmaps.get(qrIndex)); qrImage.setImageBitmap(qrBitmaps.get(qrIndex));
if(qrDelay > 0) { if(qrDelay > 0) {
this.qrIndex += 1; this.qrIndex += 1;
@@ -226,13 +167,28 @@ public class CodeGeneratorView extends Fragment {
qrIndexN.setText(String.valueOf(qrIndex+1)); qrIndexN.setText(String.valueOf(qrIndex+1));
} }
private void qrLoop(){ private void startLoop() {
timer = new CountDownTimer(minQrSpeed-Math.abs(qrDelay)+1, 1000) {
public void onTick(long millisUntilFinished) {}
public void onFinish() { final Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
try{
updateQr(); updateQr();
qrLoop();
} }
}.start(); catch (Exception e) {
AlertManager.error(e);
}
finally{
double a = ((double) maxQrSpeed) / (Math.abs(qrDelay));
a = Math.min(Math.max(a, maxQrSpeed), minQrSpeed);
handler.postDelayed(this, (long) a);
}
}
};
handler.post(runnable);
} }
} }
@@ -65,7 +65,7 @@ public class CodeOverlayView extends View {
} }
} }
if(barColors != null){ if(barColors != null){
final double width = getWidth()/barColors.length; final double width = (double) getWidth() /barColors.length;
final int top = 0; final int top = 0;
final int bottom = barHeight; final int bottom = barHeight;
@@ -3,6 +3,7 @@ package com.ridgebotics.ridgescout.utility;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.widget.Toast; import android.widget.Toast;
@@ -113,4 +114,22 @@ public class AlertManager {
}); });
} }
private static ProgressDialog loadingDialog;
public static void startLoading(String title){
((Activity) context).runOnUiThread(() -> {
if(loadingDialog != null && loadingDialog.isShowing())
loadingDialog.dismiss();
loadingDialog = ProgressDialog.show(context, title, "Please wait...");
});
}
public static void stopLoading(){
((Activity) context).runOnUiThread(() -> {
if (loadingDialog != null)
loadingDialog.cancel();
loadingDialog = null;
});
}
} }
@@ -398,9 +398,10 @@ public final class FileEditor {
Arrays.sort(filenames, (o1, o2) -> { Arrays.sort(filenames, (o1, o2) -> {
try { try {
if (!o1.contains("-") || !o2.contains("-")) if (!o1.contains("-") || !o2.contains("-"))
return 0; return o2.compareTo(o1);
return Integer.valueOf(o1.split("-")[1]).compareTo(Integer.valueOf(o2.split("-")[1])); return Integer.valueOf(o1.split("-")[1].split("\\.")[0]).compareTo(Integer.valueOf(o2.split("-")[1].split("\\.")[0]));
} catch (Exception e) { } catch (Exception e) {
AlertManager.error(e);
return 0; return 0;
} }
}); });
@@ -0,0 +1,32 @@
package com.ridgebotics.ridgescout.utility;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
// https://stackoverflow.com/questions/58767733/the-asynctask-api-is-deprecated-in-android-11-what-are-the-alternatives
public class TaskRunner {
private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
private final Handler handler = new Handler(Looper.getMainLooper());
public interface Callback<R> {
void onComplete(R result);
}
public <R> void executeAsync(Callable<R> callable, Callback<R> callback) {
executor.execute(() -> {
final R result;
try {
result = callable.call();
handler.post(() -> {
callback.onComplete(result);
});
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}