From 03a1507ce2cf0804035df9d302e00cadb4f58528 Mon Sep 17 00:00:00 2001 From: Astatin3 <77305074+Astatin3@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:47:36 -0600 Subject: [PATCH] Make settings better, work on ftp transfer --- app/build.gradle.kts | 2 + app/release/baselineProfiles/0/app-release.dm | Bin 3631 -> 0 bytes app/release/baselineProfiles/1/app-release.dm | Bin 3574 -> 0 bytes app/release/output-metadata.json | 37 -- .../ridgescout/ui/data/FieldsFragment.java | 34 +- .../ui/scouting/MatchScoutingFragment.java | 10 +- .../ui/scouting/PitScoutingFragment.java | 10 +- .../ui/settings/settingsFragment.java | 348 +++++++++--------- .../ridgescout/ui/transfer/FTPSync.java | 166 +++++++++ .../ui/transfer/TransferFragment.java | 14 +- .../ridgescout/utility/fileEditor.java | 20 +- .../ridgescout/utility/settingsManager.java | 21 +- app/src/main/res/layout/fragment_settings.xml | 173 +-------- app/src/main/res/layout/fragment_transfer.xml | 23 +- gradle/libs.versions.toml | 2 + 15 files changed, 467 insertions(+), 393 deletions(-) delete mode 100644 app/release/baselineProfiles/0/app-release.dm delete mode 100644 app/release/baselineProfiles/1/app-release.dm delete mode 100644 app/release/output-metadata.json create mode 100644 app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FTPSync.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 15ef39c..7a94b52 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.navigation.fragment) implementation(libs.navigation.ui) + implementation(libs.preference) // implementation(libs.support.annotations) testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) @@ -85,6 +86,7 @@ dependencies { implementation("org.tensorflow:tensorflow-lite-task-text:0.3.0") implementation("com.squareup.okhttp3:okhttp:4.9.0") + implementation("commons-net:commons-net:3.10.0") diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm deleted file mode 100644 index 98805d7aa6615d550d0d18dd3f8f2bcd3e0dc725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3631 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 NPPRhXG5`PS{{=I;@OuCN diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm deleted file mode 100644 index 59bcad9b93f2588be91f2cc7f461a55163c43335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3574 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 I{`da>0Ms)8?*IS* diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json deleted file mode 100644 index e74bc0a..0000000 --- a/app/release/output-metadata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "com.ridgebotics.ridgescout", - "variantName": "release", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 4, - "versionName": "0.4", - "outputFile": "app-release.apk" - } - ], - "elementType": "File", - "baselineProfiles": [ - { - "minApi": 28, - "maxApi": 30, - "baselineProfiles": [ - "baselineProfiles/1/app-release.dm" - ] - }, - { - "minApi": 31, - "maxApi": 2147483647, - "baselineProfiles": [ - "baselineProfiles/0/app-release.dm" - ] - } - ], - "minSdkVersionForDexing": 24 -} \ No newline at end of file diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldsFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldsFragment.java index 6caaa9c..5cb787a 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldsFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/data/FieldsFragment.java @@ -55,10 +55,8 @@ public class FieldsFragment extends Fragment { @Nullable Bundle savedInstanceState) { binding = FragmentDataFieldsBinding.inflate(inflater, container, false); - binding.revertVersionButton.setVisibility(View.VISIBLE); binding.valueEditScrollview.setOnTouchListener((v, event) -> true); - binding.saveButton.setVisibility(View.GONE); binding.cancelEditButton.setVisibility(View.GONE); binding.editButton.setVisibility(View.GONE); @@ -124,6 +122,25 @@ public class FieldsFragment extends Fragment { tr.setBackgroundColor(unfocused_background_color); } } + + if(values.length > 1) { + binding.revertVersionButton.setVisibility(View.VISIBLE); + binding.revertVersionButton.setOnClickListener(v -> { + AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); + alert.setTitle("Warning!"); + alert.setMessage("If there is any data set this version, it will be deleted!"); + alert.setPositiveButton("OK", (dialog, which) -> { + inputType[][] newArr = new inputType[values.length - 1][]; + System.arraycopy(values, 0, newArr, 0, values.length - 1); + if(fields.save(filename, newArr)) + AlertManager.toast("Saved"); + load_field_menu(); + }); + alert.setNegativeButton("Cancel", null); + alert.setCancelable(true); + alert.create().show(); + }); + } } private void display_fields(inputType[] version_values) { @@ -182,10 +199,7 @@ public class FieldsFragment extends Fragment { AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); alert.setTitle("Warning!"); alert.setMessage("Changing or removing some values will result in lost data!\nBut this will create a new field version, and you can revert at any time."); - alert.setPositiveButton("OK", null); - alert.setNegativeButton("Cancel", null); - alert.setCancelable(true); - alert.setOnDismissListener(b -> { + alert.setPositiveButton("OK", (dialog, which) -> { inputType[][] currentValues = fields.load(filename); assert currentValues != null; inputType[][] newValues = new inputType[currentValues.length+1][]; @@ -198,11 +212,13 @@ public class FieldsFragment extends Fragment { } // newValues[newValues.length-1] = values[values.length-1]; - boolean saved = fields.save(filename, newValues); - AlertManager.alert("Saved", String.valueOf(saved)); + if(fields.save(filename, newValues)) + AlertManager.toast("Saved"); Navigation.findNavController((Activity) getContext(), R.id.nav_host_fragment_activity_main).navigate(R.id.action_navigation_data_fields_to_navigation_data_fields_chooser); }); + alert.setNegativeButton("Cancel", null); + alert.setCancelable(true); alert.create().show(); } @@ -503,7 +519,7 @@ public class FieldsFragment extends Fragment { newValues[newValues.length-1] = field; values[values.length-1] = newValues; - AlertManager.alert("Test", String.valueOf(binding.fieldsArea.getReorderedIndexes())); +// AlertManager.alert("Test", String.valueOf(binding.fieldsArea.getReorderedIndexes())); //TableRow tr = getTableRow(field); //binding.fieldsArea.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 3d8beee..caec176 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 @@ -310,8 +310,14 @@ public class MatchScoutingFragment extends Fragment { default_fields(); set_indicator_color(unsaved_color); }else{ - get_fields(); - set_indicator_color(saved_color); + try { + get_fields(); + set_indicator_color(saved_color); + } catch (Exception e){ + AlertManager.error(e); + default_fields(); + set_indicator_color(unsaved_color); + } } asm.start(); 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 4e20b22..4975f68 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 @@ -119,8 +119,14 @@ public class PitScoutingFragment extends Fragment { default_fields(); set_indicator_color(unsaved_color); }else{ - get_fields(); - set_indicator_color(saved_color); + try { + get_fields(); + set_indicator_color(saved_color); + } catch (Exception e){ + AlertManager.error(e); + default_fields(); + set_indicator_color(unsaved_color); + } } asm.start(); 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 ff1edd5..d6b5d90 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,9 +1,22 @@ package com.ridgebotics.ridgescout.ui.settings; +import static android.text.InputType.TYPE_CLASS_NUMBER; +import static com.ridgebotics.ridgescout.utility.settingsManager.AllyPosKey; +import static com.ridgebotics.ridgescout.utility.settingsManager.FTPEnabled; +import static com.ridgebotics.ridgescout.utility.settingsManager.FTPServer; +import static com.ridgebotics.ridgescout.utility.settingsManager.SelEVCodeKey; +import static com.ridgebotics.ridgescout.utility.settingsManager.TeamNumKey; +import static com.ridgebotics.ridgescout.utility.settingsManager.UnameKey; +import static com.ridgebotics.ridgescout.utility.settingsManager.WifiModeKey; +import static com.ridgebotics.ridgescout.utility.settingsManager.defaults; +import static com.ridgebotics.ridgescout.utility.settingsManager.getEditor; +import static com.ridgebotics.ridgescout.utility.settingsManager.prefs; + import android.app.AlertDialog; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,11 +26,14 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.Spinner; +import android.widget.TableRow; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.google.android.material.divider.MaterialDivider; import com.ridgebotics.ridgescout.databinding.FragmentSettingsBinding; import com.ridgebotics.ridgescout.types.data.intType; import com.ridgebotics.ridgescout.utility.fileEditor; @@ -27,9 +43,14 @@ import com.skydoves.powerspinner.IconSpinnerAdapter; import com.skydoves.powerspinner.IconSpinnerItem; import com.skydoves.powerspinner.OnSpinnerItemSelectedListener; import com.skydoves.powerspinner.PowerSpinnerView; +import com.skydoves.powerspinner.SpinnerGravity; + +import org.checkerframework.checker.units.qual.C; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; public class settingsFragment extends Fragment { @@ -37,9 +58,21 @@ public class settingsFragment extends Fragment { private android.widget.ScrollView ScrollArea; private android.widget.TableLayout Table; - private void setDropdownItems(Spinner dropdown, String[] items){ - ArrayAdapter adapter = new ArrayAdapter<>(requireActivity(), android.R.layout.simple_spinner_item, items); - dropdown.setAdapter(adapter); +// private void setDropdownItems(Spinner dropdown, String[] items){ +// ArrayAdapter adapter = new ArrayAdapter<>(requireActivity(), android.R.layout.simple_spinner_item, items); +// dropdown.setAdapter(adapter); +// } + + private View[] concatArrays(View[] a, View[] b){ + return Stream.of(a, b).flatMap(Stream::of).toArray(View[]::new); + } + + + private View[] addViews(View[] a){ + for(int i = 0; i < a.length; i++){ + binding.SettingsTable.addView(a[i]); + } + return a; } private int safeToInt(String num){ @@ -52,6 +85,144 @@ public class settingsFragment extends Fragment { } } + private View[] createHeading(String name){ + TextView tv = new TextView(getContext()); + tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); + TableRow.LayoutParams params = new TableRow.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + params.topMargin = 100; + tv.setLayoutParams(params); + tv.setTextSize(20); + tv.setText(name); + + View divider = new MaterialDivider(getContext()); + + return new View[]{tv, divider}; + } + + private View[] addStringEdit(String name, String key){ + View[] heading = createHeading(name); + EditText et = new EditText(getContext()); + et.setText(prefs.getString(key, (String) defaults.get(key))); + + et.addTextChangedListener(new TextWatcher() { + + public void afterTextChanged(Editable s) { + getEditor().putString(key, s.toString()).apply(); + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void onTextChanged(CharSequence s, int start, int before, int count) {} + }); + return concatArrays(heading, new View[]{et}); + } + + private View[] addNumberEdit(String name, String key){ + View[] heading = createHeading(name); + EditText et = new EditText(getContext()); + et.setText(String.valueOf(prefs.getInt(key, (Integer) defaults.get(key)))); + et.setInputType(TYPE_CLASS_NUMBER); + + et.addTextChangedListener(new TextWatcher() { + + public void afterTextChanged(Editable s) { + getEditor().putInt(key, safeToInt(s.toString())).apply(); + } + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void onTextChanged(CharSequence s, int start, int before, int count) {} + }); + return concatArrays(heading, new View[]{et}); + } + + private PowerSpinnerView addDropdownEdit(String name, String[] options, String key){ + PowerSpinnerView dropdown = new PowerSpinnerView(getContext()); + + List iconSpinnerItems = new ArrayList<>(); + for(int i = 0; i < options.length; i++){ + iconSpinnerItems.add(new IconSpinnerItem(options[i])); + } + IconSpinnerAdapter iconSpinnerAdapter = new IconSpinnerAdapter(dropdown); + + dropdown.setGravity(Gravity.CENTER); + + dropdown.setSpinnerAdapter(iconSpinnerAdapter); + dropdown.setItems(iconSpinnerItems); + dropdown.setHint("Unselected"); + + dropdown.setPadding(10,20,10,20); + dropdown.setBackgroundColor(0xf0000000); + dropdown.setTextColor(0xff00ff00); + dropdown.setTextSize(14.5f); + dropdown.setArrowGravity(SpinnerGravity.END); + dropdown.setArrowPadding(8); + dropdown.setSpinnerPopupElevation(14); + + return dropdown; + } + + private View[] addDropdownByString(String name, String[] options, String key){ + View[] heading = createHeading(name); + PowerSpinnerView dropdown = addDropdownEdit(name, options, key); + int index = Arrays.asList(options).indexOf(prefs.getString(key, (String) defaults.get(key))); + System.out.println(index); + + if(options.length != 0 && index != -1){ + dropdown.selectItemByIndex(index); + } + + dropdown.setOnSpinnerItemSelectedListener( + (OnSpinnerItemSelectedListener) + (oldIndex, oldItem, newIndex, newItem) -> getEditor().putString(key, newItem.getText().toString()).apply() + ); + + return concatArrays(heading, new View[]{dropdown}); + } + + private View[] addDropdownByIndex(String name, String[] options, String key){ + View[] heading = createHeading(name); + PowerSpinnerView dropdown = addDropdownEdit(name, options, key); + + int index = prefs.getInt(key, (Integer) defaults.get(key)); + + if(dropdown.length() != 0 && index != -1){ + dropdown.selectItemByIndex(index); + } + + dropdown.setOnSpinnerItemSelectedListener( + (OnSpinnerItemSelectedListener) + (oldIndex, oldItem, newIndex, newItem) -> getEditor().putInt(key, newIndex).apply() + ); + + return concatArrays(heading, new View[]{dropdown}); + } + + private View[] addCheckbox(String name, String key, View[] dependency){ + CheckBox cb = new CheckBox(getContext()); + cb.setText(name); + cb.setTextSize(22); + boolean checked = prefs.getBoolean(key, (Boolean) defaults.get(key)); + cb.setChecked(checked); + + if(dependency != null && !checked){ + for(int i = 0; i < dependency.length; i++){ + dependency[i].setVisibility(View.GONE); + } + } + + cb.setOnCheckedChangeListener((buttonView, isChecked) -> { + getEditor().putBoolean(key, isChecked).apply(); + if(dependency != null){ + for(int i = 0; i < dependency.length; i++){ + dependency[i].setVisibility(isChecked ? View.VISIBLE : View.GONE); + System.out.println(dependency[i]); + } + } + }); + + return new View[]{new MaterialDivider(getContext()), cb}; + } public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -59,172 +230,19 @@ public class settingsFragment extends Fragment { binding = FragmentSettingsBinding.inflate(inflater, container, false); View root = binding.getRoot(); - EditText username = binding.username; - username.setText(settingsManager.getUsername()); - username.addTextChangedListener(new TextWatcher() { - - public void afterTextChanged(Editable s) { - settingsManager.setUsername(username.getText().toString()); - } - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - public void onTextChanged(CharSequence s, int start, int before, int count) {} - }); - - - - - PowerSpinnerView spinnerView = binding.eventDropdown; - - List iconSpinnerItems = new ArrayList<>(); - - String target_event_name = settingsManager.getEVCode(); - int target_index = -1; - - ArrayList evlist = fileEditor.getEventList(); - for(int i = 0; i < evlist.size(); i++){ - if(evlist.get(i).equals(target_event_name)){ - target_index = i; - } - iconSpinnerItems.add(new IconSpinnerItem(evlist.get(i))); - } - - IconSpinnerAdapter iconSpinnerAdapter = new IconSpinnerAdapter(spinnerView); - spinnerView.setSpinnerAdapter(iconSpinnerAdapter); - spinnerView.setItems(iconSpinnerItems); -// spinnerView.setLifecycleOwner(this); - - if(!iconSpinnerItems.isEmpty() && target_index != -1){ - spinnerView.selectItemByIndex(target_index); - } - - spinnerView.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener() { - @Override - public void onItemSelected(int oldIndex, @Nullable IconSpinnerItem oldItem, int newIndex, - IconSpinnerItem newItem) { - settingsManager.setEVCode(newItem.getText().toString()); - } - }); - - - - - - - - - - - PowerSpinnerView alliance_pos_spinnerView = binding.alliancePosDropdown; - - List alliance_pos_iconSpinnerItems = new ArrayList<>(); - - String target_alliance_pos = settingsManager.getAllyPos(); - int alliance_pos_target_index = -1; - String[] alliance_pos_list = new String[]{"red-1", "red-2", "red-3", "blue-1", "blue-2", "blue-3"}; - for(int i = 0; i < alliance_pos_list.length; i++){ - if(alliance_pos_list[i].equals(target_alliance_pos)){ - alliance_pos_target_index = i; - } - alliance_pos_iconSpinnerItems.add(new IconSpinnerItem(alliance_pos_list[i])); - } + addViews(addStringEdit("Username", UnameKey)); + addViews(addDropdownByString("Event Code", fileEditor.getEventList().toArray(new String[0]), SelEVCodeKey)); + addViews(addDropdownByString("Alliance Position", alliance_pos_list, AllyPosKey)); + addViews(addNumberEdit("Team Number", TeamNumKey)); - IconSpinnerAdapter alliance_pos_iconSpinnerAdapter = new IconSpinnerAdapter(alliance_pos_spinnerView); - alliance_pos_spinnerView.setSpinnerAdapter(alliance_pos_iconSpinnerAdapter); - alliance_pos_spinnerView.setItems(alliance_pos_iconSpinnerItems); - alliance_pos_spinnerView.setLifecycleOwner(this); - - if(alliance_pos_target_index != -1){ - alliance_pos_spinnerView.selectItemByIndex(alliance_pos_target_index); - } - - alliance_pos_spinnerView.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener() { - @Override - public void onItemSelected(int oldIndex, @Nullable IconSpinnerItem oldItem, int newIndex, - IconSpinnerItem newItem) { - settingsManager.setAllyPos(newItem.getText().toString()); - } - }); - - - - - - - - - -// -// CheckBox practice_mode = binding.practiceMode; -// practice_mode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { -// @Override -// public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { -// latestSettings.settings.set_practice_mode(isChecked); -// } -// -// }); -// -// practice_mode.setChecked(latestSettings.settings.get_practice_mode()); - - - - EditText team_num = binding.teamNumber; - team_num.setText(String.valueOf(settingsManager.getTeamNum())); - team_num.addTextChangedListener(new TextWatcher() { - - public void afterTextChanged(Editable s) { - settingsManager.setTeamNum(safeToInt(team_num.getText().toString())); - } - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - public void onTextChanged(CharSequence s, int start, int before, int count) {} - }); - - - - - - - CheckBox wifi_mode = binding.wifiMode; - wifi_mode.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { - settingsManager.setWifiMode(isChecked); - } - - }); - - wifi_mode.setChecked(settingsManager.getWifiMode()); - - - - - - - Button reset_button = binding.resetButton; - reset_button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - AlertDialog.Builder alert = new AlertDialog.Builder(getContext()); - alert.setTitle("Warning"); - alert.setMessage("Do you really want to reset settings?"); - alert.setCancelable(true); - - alert.setPositiveButton("Ok", (dialog, which) -> { - settingsManager.resetSettings(); - username.setText(settingsManager.getUsername()); - spinnerView.clearSelectedItem(); -// practice_mode.setChecked(latestSettings.settings.get_practice_mode()); - wifi_mode.setChecked(settingsManager.getWifiMode()); - alliance_pos_spinnerView.selectItemByIndex(0); - team_num.setText(String.valueOf(settingsManager.getTeamNum())); - }); - - alert.setNegativeButton("Cancel", null); - alert.create().show(); - } - }); + View[] FTPDependency = addStringEdit("FTP Server", FTPServer); + View[] WifiDependency = addCheckbox("FTP Enabled", FTPEnabled, FTPDependency); + addViews(addCheckbox("Wifi Mode", WifiModeKey, concatArrays(FTPDependency, WifiDependency))); + addViews(WifiDependency); + addViews(FTPDependency); return root; } 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 new file mode 100644 index 0000000..4064554 --- /dev/null +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/FTPSync.java @@ -0,0 +1,166 @@ +package com.ridgebotics.ridgescout.ui.transfer; + +import static com.ridgebotics.ridgescout.utility.DataManager.evcode; +import static com.ridgebotics.ridgescout.utility.fileEditor.baseDir; + +import android.content.Context; +import android.net.Uri; + +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 org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.checkerframework.checker.units.qual.C; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.InetAddress; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +public class FTPSync extends Thread { + private static final String remoteBasePath = "/RidgeScout/"; + + public interface onResult { + void onResult(boolean error); + } + + public onResult onResult; + + public static void sync(onResult onResult){ + DataManager.reload_event(); + FTPSync ftpSync = new FTPSync(); + ftpSync.onResult = onResult; + ftpSync.start(); + } + + private class FileDate { + public String filename; + public Calendar lastModified; + } + + public boolean sendFile(FTPClient ftpClient, String filename) throws Exception{ + FileInputStream fin = new FileInputStream(baseDir + filename); + boolean worked = ftpClient.storeFile(remoteBasePath + filename, fin); + fin.close(); + return worked; + } + + public void run() { + try { + FTPClient ftpClient = new FTPClient(); + InetAddress address = InetAddress.getByName(settingsManager.getFTPServer()); + ftpClient.connect(address); + ftpClient.login("anonymous", null); + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); +// ftpClient.setFileTransferMode(FTP.BLOCK_TRANSFER_MODE) + + FTPFile[] remoteFilestmp = ftpClient.listFiles(remoteBasePath); + String[] localFilestmp = fileEditor.getEventFiles(evcode); + + FileDate[] remoteFiles = new FileDate[remoteFilestmp.length]; + for(int i = 0; i < remoteFilestmp.length; i++){ +// System.out.println(remoteFilestmp[i].getName()); + remoteFiles[i] = new FileDate(); + remoteFiles[i].filename = remoteFilestmp[i].getName(); + System.out.println(remoteFiles[i].filename); + remoteFiles[i].lastModified = remoteFilestmp[i].getTimestamp(); + } + + FileDate[] localFiles = new FileDate[localFilestmp.length]; + for(int i = 0; i < localFilestmp.length; i++){ +// sendFile(localFilestmp[i]); + String filename = "matches.fields"; + File f = new File(baseDir + filename); +// File f = new File(baseDir + localFilestmp[i]); +// System.out.println(f.exists()); +// FileInputStream fin = new FileInputStream(f); +// System.out.println(f.getName() + ", " + f.exists() + ", " + sendFile(ftpClient, f)); +// fin.close(); + +// System.out.println(ftpClient.getStatus()); + + + localFiles[i] = new FileDate(); + localFiles[i].filename = localFilestmp[i]; + localFiles[i].lastModified = fileEditor.getLastModified(localFilestmp[i]); + } + + FileAction[] localActions = compareDates(localFiles, fd2map(remoteFiles), false); + System.out.println(localActions.length); + for(int i = 0 ; i < localActions.length; i++){ + + System.out.println(localActions[i].filename + ", " + localActions[i].action); + switch (localActions[i].action){ + case SEND: + sendFile(ftpClient, localActions[i].filename); + fileEditor.setLastModified(localActions[i].filename, localActions[i].otherTimestamp); + break; + case RECIEVE: + + break; + } + } + + + } catch (Exception e) { + AlertManager.error(e); + onResult.onResult(true); + AlertManager.toast("Error Syncing!"); + } +// } finally { +// AlertManager.toast("Synced!"); +// onResult.onResult(false); +// } + } + + private Map fd2map(FileDate[] fdarr){ + Map map = new HashMap<>(); + for(int i = 0; i < fdarr.length; i++) + map.put(fdarr[i].filename, fdarr[i].lastModified); + return map; + } + + private enum SyncAction { + SEND, + RECIEVE, + NONE + } + + private class FileAction { + String filename; + SyncAction action; + Calendar otherTimestamp; + public FileAction(String filename, SyncAction action, Calendar otherTimestamp){ + this.filename = filename; + this.action = action; + this.otherTimestamp = otherTimestamp; + } + } + + private FileAction[] compareDates(FileDate[] files, Map refrence, boolean reverse){ + FileAction[] actions = new FileAction[files.length]; + for(int i = 0; i < files.length; i++){ + Calendar ref = refrence.get(files[i].filename); + + System.out.println(ref); + + if(ref == null || files[i].lastModified.after(ref)) { + actions[i] = new FileAction(files[i].filename, !reverse ? SyncAction.SEND : SyncAction.RECIEVE, ref); + }else if(files[i].lastModified.before(ref)){ + actions[i] = new FileAction(files[i].filename, !reverse ? SyncAction.RECIEVE : SyncAction.SEND, ref); + }else { + actions[i] = new FileAction(files[i].filename, SyncAction.NONE, ref); + } + } + return actions; + } +} diff --git a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TransferFragment.java b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TransferFragment.java index 979098f..b116f65 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TransferFragment.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/ui/transfer/TransferFragment.java @@ -71,6 +71,7 @@ public class TransferFragment extends Fragment { binding.uploadButton.setEnabled(false); binding.CSVButton.setEnabled(false); binding.downloadButton.setEnabled(true); + binding.SyncButton.setEnabled(false); return binding.getRoot(); } @@ -106,8 +107,19 @@ public class TransferFragment extends Fragment { builder.show(); }); - if(!settingsManager.getWifiMode()) + if(!settingsManager.getWifiMode()) { binding.TBAButton.setEnabled(false); + binding.SyncButton.setEnabled(false); + } + + if(!settingsManager.getFTPEnabled()) { + binding.SyncButton.setEnabled(false); + } + + binding.SyncButton.setOnClickListener(v -> { + binding.SyncButton.setEnabled(false); + FTPSync.sync(error -> getActivity().runOnUiThread(() -> binding.SyncButton.setEnabled(true))); + }); return binding.getRoot(); } 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 a6b90d3..f8becf4 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/fileEditor.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/fileEditor.java @@ -17,15 +17,17 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.List; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; public final class fileEditor { - private final static String baseDir = "/data/data/com.ridgebotics.ridgescout/"; + public final static String baseDir = "/data/data/com.ridgebotics.ridgescout/"; public static final byte internalDataVersion = 0x01; public static final int maxCompressedBlockSize = 4096; @@ -43,7 +45,6 @@ public final class fileEditor { } - public static char byteToChar(int num){ return new String(toBytes(num, 1), StandardCharsets.ISO_8859_1).charAt(0); } @@ -189,7 +190,22 @@ public final class fileEditor { + public static Calendar getLastModified(String filepath){ + File f = new File(baseDir + filepath); + if(f.exists()){ + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(f.lastModified()); + return calendar; + } + return null; + } + public static void setLastModified(String filepath, Calendar calendar){ + File f = new File(baseDir + filepath); + if(f.exists()){ + f.setLastModified(calendar.getTimeInMillis()); + } + } diff --git a/app/src/main/java/com/ridgebotics/ridgescout/utility/settingsManager.java b/app/src/main/java/com/ridgebotics/ridgescout/utility/settingsManager.java index 35959fa..3a9190c 100644 --- a/app/src/main/java/com/ridgebotics/ridgescout/utility/settingsManager.java +++ b/app/src/main/java/com/ridgebotics/ridgescout/utility/settingsManager.java @@ -19,6 +19,8 @@ public class settingsManager { public static final String AllyPosKey = "alliance_pos"; public static final String DataModeKey = "data_view_mode"; public static final String BtUUIDKey = "bt_uuid"; + public static final String FTPEnabled = "ftp_enabled"; + public static final String FTPServer = "ftp_server"; public static Map defaults = getDefaults(); private static Map getDefaults(){ @@ -32,11 +34,13 @@ public class settingsManager { hm.put(AllyPosKey, "red-1"); hm.put(DataModeKey, 0); hm.put(BtUUIDKey, UUID.randomUUID().toString()); + hm.put(FTPEnabled, false); + hm.put(FTPServer, "0.0.0.0"); return hm; } - private static SharedPreferences.Editor getEditor(){ + public static SharedPreferences.Editor getEditor(){ if(editor == null) editor = prefs.edit(); return editor; } @@ -45,11 +49,16 @@ public class settingsManager { getEditor() .putString(UnameKey, (String) defaults.get( UnameKey )).apply(); getEditor() .putString(SelEVCodeKey,(String) defaults.get( SelEVCodeKey)).apply(); getEditor().putBoolean(WifiModeKey, (boolean) defaults.get( WifiModeKey )).apply(); + getEditor() .putInt(TeamNumKey, (int) defaults.get( TeamNumKey )).apply(); getEditor() .putInt(MatchNumKey, (int) defaults.get( MatchNumKey )).apply(); getEditor() .putString(AllyPosKey, (String) defaults.get( AllyPosKey )).apply(); getEditor() .putInt(DataModeKey, (int) defaults.get( DataModeKey )).apply(); + getEditor() .putString(BtUUIDKey, (String) defaults.get( BtUUIDKey )).apply(); + + getEditor().putBoolean(FTPEnabled, (boolean) defaults.get(FTPEnabled )).apply(); + getEditor() .putString(FTPServer, (String) defaults.get( BtUUIDKey )).apply(); } // IDK why I decided to format these functions like this. It looks cool though. @@ -77,4 +86,14 @@ public class settingsManager { public static String getBtUUID(){return prefs.getString( BtUUIDKey, (String) defaults.get(BtUUIDKey));} public static void setBtUUID(String str){ getEditor().putString( BtUUIDKey,str).apply();} + + + public static boolean getFTPEnabled(){return prefs.getBoolean( FTPEnabled, (boolean) defaults.get(FTPEnabled));} + public static void setFTPEnabled(boolean bool){getEditor().putBoolean( FTPEnabled,bool).apply();} + + public static String getFTPServer(){return prefs.getString( FTPServer, (String) defaults.get(FTPServer));} + public static void setFTPServer(String str){ getEditor().putString( FTPServer,str).apply();} + + + } diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index a1c2b29..d415c70 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -7,7 +7,6 @@ tools:context=".ui.settings.settingsFragment"> - + android:layout_height="wrap_content"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -