diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index fc956af..94d25b2 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -13,17 +13,6 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 777f319..d5ce06d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,9 @@
+
+
+
diff --git a/app/src/main/java/com/team3663/scouting_app/activities/AppLaunch.java b/app/src/main/java/com/team3663/scouting_app/activities/AppLaunch.java
index 72a1dab..dfca5d3 100644
--- a/app/src/main/java/com/team3663/scouting_app/activities/AppLaunch.java
+++ b/app/src/main/java/com/team3663/scouting_app/activities/AppLaunch.java
@@ -30,6 +30,7 @@
import com.team3663.scouting_app.config.Constants;
import com.team3663.scouting_app.config.Globals;
import com.team3663.scouting_app.databinding.AppLaunchBinding;
+import com.team3663.scouting_app.utility.CPR_Network;
import com.team3663.scouting_app.utility.dataFile.*;
import java.util.List;
@@ -92,6 +93,10 @@ protected void onCreate(Bundle in_savedInstanceState) {
return insets;
});
+ // Default to NO internet
+ appLaunchBinding.imageInternet.setVisibility(View.INVISIBLE);
+ Globals.network = new CPR_Network(this);
+
// Display app version
PackageInfo pInfo;
try {
@@ -118,7 +123,7 @@ protected void onCreate(Bundle in_savedInstanceState) {
boolean havePerms = !perm_list.isEmpty();
// If we have storage permissions go ahead and load the data.
- // Otherwise, initiate getting permissions (which will then load the data afterwards)
+ // Otherwise, initiate getting permissions (which will then load the data afterward)
if (havePerms) {
Globals.baseStorageURI = Uri.parse(Globals.sp.getString(Constants.Prefs.STORAGE_URI, null));
initDataFiles();
@@ -128,7 +133,13 @@ protected void onCreate(Bundle in_savedInstanceState) {
// While loading Matches, we messed with Globals.CurrentMatchType, so reset it
Globals.CurrentMatchType = Constants.PreMatch.DEFAULT_MATCH_TYPE;
- }
+
+ // check if we have access to the internet
+ if (Globals.network.hasActiveInternet())
+ appLaunchBinding.imageInternet.setVisibility(View.VISIBLE);
+ else
+ appLaunchBinding.imageInternet.setVisibility(View.INVISIBLE);
+ }
// =============================================================================================
// Function: initSettings
diff --git a/app/src/main/java/com/team3663/scouting_app/activities/Settings.java b/app/src/main/java/com/team3663/scouting_app/activities/Settings.java
index 7b191d5..71e0d8c 100644
--- a/app/src/main/java/com/team3663/scouting_app/activities/Settings.java
+++ b/app/src/main/java/com/team3663/scouting_app/activities/Settings.java
@@ -4,6 +4,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.text.Editable;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
@@ -14,9 +15,7 @@
import com.team3663.scouting_app.R;
import com.team3663.scouting_app.config.*;
import com.team3663.scouting_app.databinding.SettingsBinding;
-import com.team3663.scouting_app.fragments.SettingsPagerAdapter;
-import com.team3663.scouting_app.fragments.SettingsPage1;
-import com.team3663.scouting_app.fragments.SettingsPage2;
+import com.team3663.scouting_app.fragments.*;
import com.google.android.material.tabs.TabLayoutMediator;
public class Settings extends AppCompatActivity {
@@ -70,8 +69,9 @@ private void SaveSettings() {
Intent intent = new Intent();
SettingsPage1 fragmentPage1 = adapter.getFragmentPage1();
SettingsPage2 fragmentPage2 = adapter.getFragmentPage2();
+ SettingsPage3 fragmentPage3 = adapter.getFragmentPage3();
- // Page1 & Page2 Settings
+ // Page1 Settings
if (fragmentPage1 != null && fragmentPage1.binding != null) {
int CompetitionId = Globals.CompetitionList.getCompetitionId(fragmentPage1.binding.spinnerCompetition.getSelectedItem().toString());
@@ -97,7 +97,7 @@ private void SaveSettings() {
Globals.spe.putInt(Constants.Prefs.NUM_MATCHES, NumMatches);
}
- // UI Settings
+ // Page2 Settings
if (fragmentPage2 != null && fragmentPage2.binding != null) {
int ColorId = Globals.ColorList.getColorId(fragmentPage2.binding.spinnerColor.getSelectedItem().toString());
if (ColorId > 0) {
@@ -114,6 +114,26 @@ private void SaveSettings() {
Globals.spe.putInt(Constants.Prefs.PREF_ORIENTATION, Globals.CurrentFieldOrientationPos);
}
+ // Page3 Settings
+ if (fragmentPage3 != null && fragmentPage3.binding != null) {
+ Editable userField;
+
+ userField = fragmentPage3.binding.editGoogleDrive.getText();
+ Globals.spe.putString(Constants.Prefs.GOOGLE_DRIVE, (userField != null) ? userField.toString() : "");
+
+ userField = fragmentPage3.binding.editServer.getText();
+ Globals.spe.putString(Constants.Prefs.SQL_SERVER, (userField != null) ? userField.toString() : "");
+
+ userField = fragmentPage3.binding.editDatabase.getText();
+ Globals.spe.putString(Constants.Prefs.SQL_DATABASE, (userField != null) ? userField.toString() : "");
+
+ userField = fragmentPage3.binding.editUser.getText();
+ Globals.spe.putString(Constants.Prefs.SQL_USER, (userField != null) ? userField.toString() : "");
+
+ userField = fragmentPage3.binding.editPassword.getText();
+ Globals.spe.putString(Constants.Prefs.SQL_PASSWORD, (userField != null) ? userField.toString() : "");
+ }
+
Globals.spe.apply();
setResult(RESULT_OK, intent);
finish();
diff --git a/app/src/main/java/com/team3663/scouting_app/activities/SubmitData.java b/app/src/main/java/com/team3663/scouting_app/activities/SubmitData.java
index 1f01cd8..17a322b 100644
--- a/app/src/main/java/com/team3663/scouting_app/activities/SubmitData.java
+++ b/app/src/main/java/com/team3663/scouting_app/activities/SubmitData.java
@@ -2,13 +2,22 @@
import android.annotation.SuppressLint;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.activity.EdgeToEdge;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
@@ -35,6 +44,8 @@ public class SubmitData extends AppCompatActivity {
private SubmitDataBinding submitDataBinding;
private static final Timer achievement_timer = new Timer();
private static MediaPlayer media;
+ private ConnectivityManager connectivityManager;
+ private ConnectivityManager.NetworkCallback networkCallback;
@SuppressLint({"SetTextI18n", "MissingInflatedId"})
@Override
@@ -61,6 +72,7 @@ protected void onCreate(Bundle in_savedInstanceState) {
}
// Initialize activity components that don't log anything
+ initNetwork();
initMatchType();
initMatch();
initQR();
@@ -70,6 +82,15 @@ protected void onCreate(Bundle in_savedInstanceState) {
initOverride();
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // Important: Unregister to avoid memory leaks
+ if (connectivityManager != null && networkCallback != null) {
+ connectivityManager.unregisterNetworkCallback(networkCallback);
+ }
+ }
+
// =============================================================================================
// Function: initMatchType
// Description: Initialize the Match Type field
@@ -258,6 +279,35 @@ private void closeAchievements() {
media.release();
}
+ // =============================================================================================
+ // Function: updateWifiIcon
+ // Description: Update the Wi-Fi signal icon with the correct signal strength level
+ // Parameters: in_level signal strength level
+ // Output: void
+ // =============================================================================================
+ private void updateWifiIcon(int in_level) {
+ // You would typically swap icons here based on level
+ // 0: No signal, 1-4: Signal bars
+ switch (in_level) {
+ case 4: submitDataBinding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_4); break;
+ case 3: submitDataBinding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_3); break;
+ case 2: submitDataBinding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_2); break;
+ case 1: submitDataBinding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_1); break;
+ default: submitDataBinding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_0); break;
+ }
+
+ // check if we have access to the internet
+ if (Globals.network.hasActiveInternet()) {
+ submitDataBinding.imageInternet.setVisibility(View.VISIBLE);
+ submitDataBinding.butSendGoogle.setEnabled(true);
+ submitDataBinding.butSendDatabase.setEnabled(true);
+ } else {
+ submitDataBinding.imageInternet.setVisibility(View.INVISIBLE);
+ submitDataBinding.butSendGoogle.setEnabled(false);
+ submitDataBinding.butSendDatabase.setEnabled(false);
+ }
+ }
+
// =============================================================================================
// Function: initOverride
// Description: Override team number reset for next match
@@ -320,6 +370,52 @@ private void initMatch() {
if (adp_Match.getCount() > 0) submitDataBinding.spinnerMatch.setSelection(adp_Match.getCount() - 1, true);
}
+ // =============================================================================================
+ // Function: initNetwork
+ // Description: Initialize the network related fields and process
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ private void initNetwork() {
+ // setup Wi-Fi monitoring
+ connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+
+ networkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network in_network, @NonNull NetworkCapabilities in_capabilities) {
+ int rssi = 0;
+
+ // On API 31+, WifiInfo is part of the capabilities (requires location permission)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ WifiInfo wifiInfo = (WifiInfo) in_capabilities.getTransportInfo();
+ if (wifiInfo != null) rssi = wifiInfo.getRssi();
+ } else {
+ // Fallback for API 30
+ WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ rssi = wm.getConnectionInfo().getRssi();
+ }
+
+ // Convert RSSI to a signal level (0 to 4)
+ int level = WifiManager.calculateSignalLevel(rssi, 5);
+
+ // Update UI on the main thread
+ runOnUiThread(() -> updateWifiIcon(level));
+ }
+
+ @Override
+ public void onLost(@NonNull Network in_network) {
+ // Signal lost or Wi-Fi turned off
+ runOnUiThread(() -> updateWifiIcon(-1));
+ }
+ };
+
+ connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
+ }
+
// =============================================================================================
// Function: initQR
// Description: Initialize the QR Code field
@@ -347,6 +443,9 @@ private void initQR() {
// Output: void
// =============================================================================================
private void initBluetooth() {
+ // Until we have BT working...
+ submitDataBinding.butSendBT.setEnabled(false);
+
submitDataBinding.butSendBT.setOnClickListener(view -> {
// Reset pre-Match settings for next time
Globals.numStartingGamePiece = Constants.PreMatch.STARTING_GAME_PIECES;
@@ -377,6 +476,7 @@ private void initQuit() {
.setPositiveButton(getString(R.string.submit_alert_quit_positive), (dialog, which) -> {
SubmitData.this.finishAffinity();
stopLockTask();
+ Globals.network.shutdown();
System.exit(0);
})
@@ -403,7 +503,7 @@ private void initNext() {
Globals.affectedByDefenseValue = Constants.PostMatch.AFFECTED_BY_DEFENSE_NOT_SELECTED;
Globals.isPractice = false;
- // Increases the team number so that it auto fills for the next match correctly
+ // Increases the team number so that it will autofill for the next match correctly
Globals.CurrentMatchNumber++;
Intent GoToPreMatch = new Intent(SubmitData.this, PreMatch.class);
diff --git a/app/src/main/java/com/team3663/scouting_app/config/Constants.java b/app/src/main/java/com/team3663/scouting_app/config/Constants.java
index 52bb622..9b689a8 100644
--- a/app/src/main/java/com/team3663/scouting_app/config/Constants.java
+++ b/app/src/main/java/com/team3663/scouting_app/config/Constants.java
@@ -121,6 +121,11 @@ public static class Prefs {
public static final String STORAGE_URI = "StorageURI";
public static final String PREF_ORIENTATION = "PreferredFieldOrientation";
public static final String QR_SIZE = "PreferredQRSize";
+ public static final String GOOGLE_DRIVE = "PreferredGoogleDrive";
+ public static final String SQL_SERVER = "PreferredSqlServer";
+ public static final String SQL_DATABASE = "PreferredSqlDatabase";
+ public static final String SQL_USER = "PreferredSqlUser";
+ public static final String SQL_PASSWORD = "PreferredSqlPassword";
}
public static class Settings {
diff --git a/app/src/main/java/com/team3663/scouting_app/config/Globals.java b/app/src/main/java/com/team3663/scouting_app/config/Globals.java
index 25e321f..d2ca9be 100644
--- a/app/src/main/java/com/team3663/scouting_app/config/Globals.java
+++ b/app/src/main/java/com/team3663/scouting_app/config/Globals.java
@@ -9,6 +9,7 @@
import com.team3663.scouting_app.data.Accuracy;
import com.team3663.scouting_app.data.ClimbLevel;
import com.team3663.scouting_app.data.ClimbPosition;
+import com.team3663.scouting_app.utility.CPR_Network;
import com.team3663.scouting_app.utility.Logger;
import com.team3663.scouting_app.utility.achievements.Achievements;
import com.team3663.scouting_app.utility.dataFile.*;
@@ -73,4 +74,6 @@ public class Globals {
public static int TransmitMatchNum;
public static String TransmitMatchType;
+
+ public static CPR_Network network;
}
diff --git a/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPage3.java b/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPage3.java
new file mode 100644
index 0000000..db70623
--- /dev/null
+++ b/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPage3.java
@@ -0,0 +1,163 @@
+package com.team3663.scouting_app.fragments;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.team3663.scouting_app.R;
+import com.team3663.scouting_app.config.Constants;
+import com.team3663.scouting_app.config.Globals;
+import com.team3663.scouting_app.databinding.FragmentSettingsPage3Binding;
+
+public class SettingsPage3 extends Fragment {
+ public FragmentSettingsPage3Binding binding;
+ private ConnectivityManager connectivityManager;
+ private ConnectivityManager.NetworkCallback networkCallback;
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ binding = FragmentSettingsPage3Binding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ initNetwork();
+ initChooseWifi();
+ initRefresh();
+ initFields();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ // Important: Unregister to avoid memory leaks
+ if (connectivityManager != null && networkCallback != null) {
+ connectivityManager.unregisterNetworkCallback(networkCallback);
+ }
+ }
+
+ // =============================================================================================
+ // Function: initFields
+ // Description: Initialize the Fields on the fragment
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ private void initFields() {
+ binding.editGoogleDrive.setText(Globals.sp.getString(Constants.Prefs.GOOGLE_DRIVE, "1DB3Dg9N-4zbOxV28Sor-YJrWuoK_OfY8"));
+ binding.editServer.setText(Globals.sp.getString(Constants.Prefs.SQL_SERVER, "mssql01.cpr3663.io"));
+ binding.editDatabase.setText(Globals.sp.getString(Constants.Prefs.SQL_DATABASE, "CPR_Scouting_2025"));
+ binding.editUser.setText(Globals.sp.getString(Constants.Prefs.SQL_USER, "CPR_Tablet"));
+ binding.editPassword.setText(Globals.sp.getString(Constants.Prefs.SQL_PASSWORD, "Aa98o1nTlHb4sg2u0YB2eNHxVfU4n17z"));
+ }
+
+ // =============================================================================================
+ // Function: initRefresh
+ // Description: Initialize the Refresh button
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ private void initRefresh() {
+ binding.butRefresh.setOnClickListener(view -> {
+ if (Globals.network.hasActiveInternet()) {
+ binding.imageInternet.setVisibility(View.VISIBLE);
+ } else {
+ binding.imageInternet.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+
+ // =============================================================================================
+ // Function: initChooseWifi
+ // Description: Initialize the ChooseWifi button
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ private void initChooseWifi() {
+ binding.butChoose.setOnClickListener(view -> Globals.network.pickWIFI());
+ }
+
+ // =============================================================================================
+ // Function: updateWifiIcon
+ // Description: Update the Wi-Fi signal icon with the correct signal strength level
+ // Parameters: in_level signal strength level
+ // Output: void
+ // =============================================================================================
+ private void updateWifiIcon(int in_level) {
+ // You would typically swap icons here based on level
+ // 0: No signal, 1-4: Signal bars
+ switch (in_level) {
+ case 4: binding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_4); break;
+ case 3: binding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_3); break;
+ case 2: binding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_2); break;
+ case 1: binding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_1); break;
+ default: binding.imageWifiSignal.setImageResource(R.drawable.wifi_bar_0); break;
+ }
+
+ // check if we have access to the internet
+ if (Globals.network.hasActiveInternet()) {
+ binding.imageInternet.setVisibility(View.VISIBLE);
+ } else {
+ binding.imageInternet.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ // =============================================================================================
+ // Function: initNetwork
+ // Description: Initialize the network related fields and process
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ private void initNetwork() {
+ // setup Wi-Fi monitoring
+ connectivityManager = (ConnectivityManager) requireContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+
+ networkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(@NonNull Network in_network, @NonNull NetworkCapabilities in_capabilities) {
+ int rssi = 0;
+
+ // On API 31+, WifiInfo is part of the capabilities (requires location permission)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ WifiInfo wifiInfo = (WifiInfo) in_capabilities.getTransportInfo();
+ if (wifiInfo != null) rssi = wifiInfo.getRssi();
+ } else {
+ // Fallback for API 30
+ WifiManager wm = (WifiManager) requireContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ rssi = wm.getConnectionInfo().getRssi();
+ }
+
+ // Convert RSSI to a signal level (0 to 4)
+ int level = WifiManager.calculateSignalLevel(rssi, 5);
+
+ // Update UI on the main thread
+ requireActivity().runOnUiThread(() -> updateWifiIcon(level));
+ }
+
+ @Override
+ public void onLost(@NonNull Network in_network) {
+ // Signal lost or Wi-Fi turned off
+ requireActivity().runOnUiThread(() -> updateWifiIcon(-1));
+ }
+ };
+
+ connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPagerAdapter.java b/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPagerAdapter.java
index 645527f..de1e85b 100644
--- a/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPagerAdapter.java
+++ b/app/src/main/java/com/team3663/scouting_app/fragments/SettingsPagerAdapter.java
@@ -8,6 +8,7 @@
public class SettingsPagerAdapter extends FragmentStateAdapter {
private SettingsPage1 fragmentPage1;
private SettingsPage2 fragmentPage2;
+ private SettingsPage3 fragmentPage3;
public SettingsPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
@@ -26,6 +27,10 @@ public Fragment createFragment(int position) {
fragmentPage2 = new SettingsPage2();
frag = fragmentPage2;
break;
+ case 2:
+ fragmentPage3 = new SettingsPage3();
+ frag = fragmentPage3;
+ break;
}
return frag;
@@ -33,14 +38,16 @@ public Fragment createFragment(int position) {
@Override
public int getItemCount() {
- return 2;
+ return 3;
}
public SettingsPage1 getFragmentPage1() {
return fragmentPage1;
}
-
public SettingsPage2 getFragmentPage2() {
return fragmentPage2;
}
+ public SettingsPage3 getFragmentPage3() {
+ return fragmentPage3;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/team3663/scouting_app/utility/CPR_Network.java b/app/src/main/java/com/team3663/scouting_app/utility/CPR_Network.java
new file mode 100644
index 0000000..e42c8f6
--- /dev/null
+++ b/app/src/main/java/com/team3663/scouting_app/utility/CPR_Network.java
@@ -0,0 +1,156 @@
+package com.team3663.scouting_app.utility;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+// =============================================================================================
+// Class: CPR_Network
+// Description: Custom class to handle network related tasks.
+// =============================================================================================
+public class CPR_Network {
+ // =============================================================================================
+ // Class Globals
+ // =============================================================================================
+ private final Context appContext;
+ private final ExecutorService executor;
+ private final Handler mainHandler;
+
+ // =============================================================================================
+ // Why a reachability check ended the way it did.
+ // =============================================================================================
+ public enum Result {
+ REACHABLE, // socket connected successfully
+ NO_NETWORK, // device has no active internet-capable network
+ HOST_UNREACHABLE // network exists, but host:port could not be reached
+ }
+
+ public interface Callback {
+ void onResult(@NonNull Result result);
+ }
+
+ // Constructor: create the new Network object
+ public CPR_Network(@NonNull Context in_context) {
+ // Use the application context to avoid leaking an Activity.
+ this.appContext = in_context.getApplicationContext();
+ this.executor = Executors.newCachedThreadPool();
+ this.mainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ // =============================================================================================
+ // Function: check (step 1)
+ // Description: Asynchronously checks reachability. The callback is invoked on the main thread.
+ // Parameters: in_host hostname or IP of the remote machine
+ // in_port port the remote service listens on (e.g. 8080, 443, 22)
+ // in_timeoutMs socket connect timeout in milliseconds
+ // in_callback invoked on the main thread with the result
+ // Output: void
+ // =============================================================================================
+ public void check(@NonNull String in_host, int in_port, int in_timeoutMs, @NonNull Callback in_callback) {
+ executor.execute(() -> {
+ Result result = checkBlocking(in_host, in_port, in_timeoutMs);
+ mainHandler.post(() -> in_callback.onResult(result));
+ });
+ }
+
+ // =============================================================================================
+ // Function: checkBlocking
+ // Description: Synchronous check. Must NOT be called on the main thread (blocks on socket I/O).
+ // Exposed for use inside your own background threads / coroutines.
+ // Parameters: in_host hostname or IP of the remote machine
+ // in_port port the remote service listens on (e.g. 8080, 443, 22)
+ // in_timeoutMs socket connect timeout in milliseconds
+ // Output: void
+ // =============================================================================================
+ @NonNull
+ public Result checkBlocking(@NonNull String in_host, int in_port, int in_timeoutMs) {
+ if (!hasActiveInternet()) {
+ return Result.NO_NETWORK;
+ }
+ return isHostReachable(in_host, in_port, in_timeoutMs)
+ ? Result.REACHABLE
+ : Result.HOST_UNREACHABLE;
+ }
+
+ // =============================================================================================
+ // Function: hasActiveInternet
+ // Description: is there an active, internet-capable network?
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ public boolean hasActiveInternet() {
+ ConnectivityManager cm =
+ (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm == null) {
+ return false;
+ }
+ Network network = cm.getActiveNetwork();
+ if (network == null) {
+ return false;
+ }
+ NetworkCapabilities caps = cm.getNetworkCapabilities(network);
+ return caps != null
+ && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ // =============================================================================================
+ // Function: isHostReachable (step 2)
+ // Description: can we open a TCP connection to host:port?
+ // Parameters: in_host hostname or IP of the remote machine
+ // in_port port the remote service listens on (e.g. 8080, 443, 22)
+ // in_timeoutMs socket connect timeout in milliseconds
+ // Output: void
+ // =============================================================================================
+ private boolean isHostReachable(@NonNull String in_host, int in_port, int in_timeoutMs) {
+ try (Socket socket = new Socket()) {
+ socket.connect(new InetSocketAddress(in_host, in_port), in_timeoutMs);
+ return true;
+ } catch (IOException e) {
+ // unreachable, connection refused, or timed out
+ return false;
+ }
+ }
+
+ // =============================================================================================
+ // Function: shutdown
+ // Description: Call when you're done (e.g. in onDestroy) to release the thread pool.
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ public void shutdown() {
+ executor.shutdownNow();
+ }
+
+ // =============================================================================================
+ // Function: pickWIFI
+ // Description: Choose new wifi to connect to
+ // Parameters: void
+ // Output: void
+ // =============================================================================================
+ public void pickWIFI() {
+ try {
+ // Try quick Wi-Fi panel
+ Intent GoToSystemWIFI = new Intent(Settings.Panel.ACTION_WIFI);
+ GoToSystemWIFI.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ appContext.startActivity(GoToSystemWIFI);
+ } catch (Exception e) {
+ // Fallback to full Wi-Fi settings
+ Intent GoToSystemWIFI = new Intent(Settings.ACTION_WIFI_SETTINGS);
+ GoToSystemWIFI.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ appContext.startActivity(GoToSystemWIFI);
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/internet.xml b/app/src/main/res/drawable/internet.xml
new file mode 100644
index 0000000..c0408de
--- /dev/null
+++ b/app/src/main/res/drawable/internet.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/wifi_bar_0.xml b/app/src/main/res/drawable/wifi_bar_0.xml
new file mode 100644
index 0000000..a3ceb95
--- /dev/null
+++ b/app/src/main/res/drawable/wifi_bar_0.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/wifi_bar_1.xml b/app/src/main/res/drawable/wifi_bar_1.xml
new file mode 100644
index 0000000..d1c28f7
--- /dev/null
+++ b/app/src/main/res/drawable/wifi_bar_1.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wifi_bar_2.xml b/app/src/main/res/drawable/wifi_bar_2.xml
new file mode 100644
index 0000000..1bf8f25
--- /dev/null
+++ b/app/src/main/res/drawable/wifi_bar_2.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wifi_bar_3.xml b/app/src/main/res/drawable/wifi_bar_3.xml
new file mode 100644
index 0000000..0b53c28
--- /dev/null
+++ b/app/src/main/res/drawable/wifi_bar_3.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/wifi_bar_4.xml b/app/src/main/res/drawable/wifi_bar_4.xml
new file mode 100644
index 0000000..6354f09
--- /dev/null
+++ b/app/src/main/res/drawable/wifi_bar_4.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/app_launch.xml b/app/src/main/res/layout/app_launch.xml
index 3fad18c..d9106f6 100644
--- a/app/src/main/res/layout/app_launch.xml
+++ b/app/src/main/res/layout/app_launch.xml
@@ -20,6 +20,16 @@
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingConstraints,VisualLintOverlap" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/submit_data.xml b/app/src/main/res/layout/submit_data.xml
index 880ad34..9dcc460 100644
--- a/app/src/main/res/layout/submit_data.xml
+++ b/app/src/main/res/layout/submit_data.xml
@@ -8,113 +8,149 @@
android:background="@color/cpr_bkgnd"
tools:context=".activities.SubmitData">
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+ android:layout_gravity="center_vertical"
+ android:orientation="horizontal">
+
+
+
+
+
+
-
-
+ android:layout_height="match_parent"
+ android:layout_weight="1.5"
+ android:layout_marginTop="40dp"
+ android:orientation="vertical">
-
-
-
-
-
+
+
+
+
+
diff --git a/app/src/main/res/values/strings_settings.xml b/app/src/main/res/values/strings_settings.xml
index 7440e2e..f14b283 100644
--- a/app/src/main/res/values/strings_settings.xml
+++ b/app/src/main/res/values/strings_settings.xml
@@ -10,4 +10,13 @@
Field orientation:
Shadow Mode:
QR Code Size:
+ Wi-Fi Connectivity
+ Refresh
+ Choose Wi-Fi
+ Google Drive ID
+ SQL Server Name
+ SQL Database
+ SQL User
+ SQL Password
+
diff --git a/app/src/main/res/values/strings_submit.xml b/app/src/main/res/values/strings_submit.xml
index 8a6089a..ac65efd 100644
--- a/app/src/main/res/values/strings_submit.xml
+++ b/app/src/main/res/values/strings_submit.xml
@@ -6,4 +6,7 @@
Quit app
Are you sure you want to Quit the app?
Quit
+ Wi-Fi signal strength
+ Internet access
+ Match Type: