Spaces:
Sleeping
Sleeping
Fadhili Sumaye commited on
Commit ·
e1b9530
1
Parent(s): 5d2afe2
Enhance frontend UX: add ripple feedback, placeholder text, loading progress indicator, button blocking, and auto-scrolling
Browse files
app/src/main/java/com/example/pestdetection/MainActivity.java
CHANGED
|
@@ -32,6 +32,13 @@ public class MainActivity extends AppCompatActivity {
|
|
| 32 |
Uri imageUri;
|
| 33 |
EditText etServerUrl;
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
private ConnectivityManager connectivityManager;
|
| 36 |
private ConnectivityManager.NetworkCallback networkCallback;
|
| 37 |
|
|
@@ -40,6 +47,12 @@ public class MainActivity extends AppCompatActivity {
|
|
| 40 |
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
| 41 |
imageUri = result.getData().getData();
|
| 42 |
imageView.setImageURI(imageUri);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
});
|
| 45 |
|
|
@@ -47,6 +60,12 @@ public class MainActivity extends AppCompatActivity {
|
|
| 47 |
registerForActivityResult(new ActivityResultContracts.TakePicture(), success -> {
|
| 48 |
if (success) {
|
| 49 |
imageView.setImageURI(imageUri);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
});
|
| 52 |
|
|
@@ -64,10 +83,14 @@ public class MainActivity extends AppCompatActivity {
|
|
| 64 |
super.onCreate(savedInstanceState);
|
| 65 |
setContentView(R.layout.activity_main);
|
| 66 |
|
|
|
|
| 67 |
imageView = findViewById(R.id.imageView);
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
| 71 |
resultText = findViewById(R.id.resultText);
|
| 72 |
treatmentText = findViewById(R.id.treatmentText);
|
| 73 |
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
|
|
@@ -81,6 +104,10 @@ public class MainActivity extends AppCompatActivity {
|
|
| 81 |
ImageView settingsToggleArrow = findViewById(R.id.settingsToggleArrow);
|
| 82 |
Button btnTestConnection = findViewById(R.id.btnTestConnection);
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
etServerUrl.setText(ApiConfig.getPredictUrl(this));
|
| 85 |
|
| 86 |
settingsHeader.setOnClickListener(v -> {
|
|
@@ -117,7 +144,19 @@ public class MainActivity extends AppCompatActivity {
|
|
| 117 |
protected void onStop() {
|
| 118 |
super.onStop();
|
| 119 |
if (imageView != null) {
|
| 120 |
-
imageView.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
}
|
| 122 |
if (resultText != null) {
|
| 123 |
resultText.setText("Waiting for scan...");
|
|
@@ -279,6 +318,13 @@ public class MainActivity extends AppCompatActivity {
|
|
| 279 |
resultText.setText("Analyzing...");
|
| 280 |
treatmentText.setText("Please wait...");
|
| 281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
// Scaled and compressed image bytes on-device
|
| 283 |
byte[] imageBytes = getScaledAndCompressedImage(imageUri);
|
| 284 |
|
|
@@ -288,6 +334,18 @@ public class MainActivity extends AppCompatActivity {
|
|
| 288 |
resultText.setText(pest + " (" + (int) (confidence * 100) + "%)");
|
| 289 |
treatmentText.setText(treatment);
|
| 290 |
updateConnectionStatus(true, "Connected");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
}
|
| 292 |
|
| 293 |
@Override
|
|
@@ -295,6 +353,14 @@ public class MainActivity extends AppCompatActivity {
|
|
| 295 |
resultText.setText("FAILED: " + message);
|
| 296 |
treatmentText.setText("Check server URL in Connection Settings");
|
| 297 |
updateConnectionStatus(false, message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
android.util.Log.e("PestScanner", "Connection Error to " + serverUrl + ": " + message);
|
| 299 |
}
|
| 300 |
});
|
|
@@ -302,6 +368,13 @@ public class MainActivity extends AppCompatActivity {
|
|
| 302 |
} catch (Exception e) {
|
| 303 |
e.printStackTrace();
|
| 304 |
resultText.setText("Error: " + e.getMessage());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
}
|
| 306 |
}
|
| 307 |
|
|
|
|
| 32 |
Uri imageUri;
|
| 33 |
EditText etServerUrl;
|
| 34 |
|
| 35 |
+
// View references for enhanced UX
|
| 36 |
+
ScrollView mainScrollView;
|
| 37 |
+
TextView tvImagePlaceholder;
|
| 38 |
+
View loadingOverlay;
|
| 39 |
+
ProgressBar progressBar;
|
| 40 |
+
Button btnSelect, btnCamera, btnUpload;
|
| 41 |
+
|
| 42 |
private ConnectivityManager connectivityManager;
|
| 43 |
private ConnectivityManager.NetworkCallback networkCallback;
|
| 44 |
|
|
|
|
| 47 |
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
| 48 |
imageUri = result.getData().getData();
|
| 49 |
imageView.setImageURI(imageUri);
|
| 50 |
+
if (tvImagePlaceholder != null) {
|
| 51 |
+
tvImagePlaceholder.setVisibility(View.GONE);
|
| 52 |
+
}
|
| 53 |
+
if (btnUpload != null) {
|
| 54 |
+
btnUpload.setEnabled(true);
|
| 55 |
+
}
|
| 56 |
}
|
| 57 |
});
|
| 58 |
|
|
|
|
| 60 |
registerForActivityResult(new ActivityResultContracts.TakePicture(), success -> {
|
| 61 |
if (success) {
|
| 62 |
imageView.setImageURI(imageUri);
|
| 63 |
+
if (tvImagePlaceholder != null) {
|
| 64 |
+
tvImagePlaceholder.setVisibility(View.GONE);
|
| 65 |
+
}
|
| 66 |
+
if (btnUpload != null) {
|
| 67 |
+
btnUpload.setEnabled(true);
|
| 68 |
+
}
|
| 69 |
}
|
| 70 |
});
|
| 71 |
|
|
|
|
| 83 |
super.onCreate(savedInstanceState);
|
| 84 |
setContentView(R.layout.activity_main);
|
| 85 |
|
| 86 |
+
mainScrollView = findViewById(R.id.mainScrollView);
|
| 87 |
imageView = findViewById(R.id.imageView);
|
| 88 |
+
tvImagePlaceholder = findViewById(R.id.tvImagePlaceholder);
|
| 89 |
+
loadingOverlay = findViewById(R.id.loadingOverlay);
|
| 90 |
+
progressBar = findViewById(R.id.progressBar);
|
| 91 |
+
btnSelect = findViewById(R.id.btnSelect);
|
| 92 |
+
btnCamera = findViewById(R.id.btnCamera);
|
| 93 |
+
btnUpload = findViewById(R.id.btnUpload);
|
| 94 |
resultText = findViewById(R.id.resultText);
|
| 95 |
treatmentText = findViewById(R.id.treatmentText);
|
| 96 |
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
|
|
|
|
| 104 |
ImageView settingsToggleArrow = findViewById(R.id.settingsToggleArrow);
|
| 105 |
Button btnTestConnection = findViewById(R.id.btnTestConnection);
|
| 106 |
|
| 107 |
+
if (btnUpload != null) {
|
| 108 |
+
btnUpload.setEnabled(false); // Initially disable upload button
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
etServerUrl.setText(ApiConfig.getPredictUrl(this));
|
| 112 |
|
| 113 |
settingsHeader.setOnClickListener(v -> {
|
|
|
|
| 144 |
protected void onStop() {
|
| 145 |
super.onStop();
|
| 146 |
if (imageView != null) {
|
| 147 |
+
imageView.setImageDrawable(null);
|
| 148 |
+
}
|
| 149 |
+
if (tvImagePlaceholder != null) {
|
| 150 |
+
tvImagePlaceholder.setVisibility(View.VISIBLE);
|
| 151 |
+
}
|
| 152 |
+
if (btnUpload != null) {
|
| 153 |
+
btnUpload.setEnabled(false);
|
| 154 |
+
}
|
| 155 |
+
if (progressBar != null) {
|
| 156 |
+
progressBar.setVisibility(View.GONE);
|
| 157 |
+
}
|
| 158 |
+
if (loadingOverlay != null) {
|
| 159 |
+
loadingOverlay.setVisibility(View.GONE);
|
| 160 |
}
|
| 161 |
if (resultText != null) {
|
| 162 |
resultText.setText("Waiting for scan...");
|
|
|
|
| 318 |
resultText.setText("Analyzing...");
|
| 319 |
treatmentText.setText("Please wait...");
|
| 320 |
|
| 321 |
+
// Show loaders and disable action controls to avoid duplicate clicks
|
| 322 |
+
if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
|
| 323 |
+
if (loadingOverlay != null) loadingOverlay.setVisibility(View.VISIBLE);
|
| 324 |
+
if (btnUpload != null) btnUpload.setEnabled(false);
|
| 325 |
+
if (btnSelect != null) btnSelect.setEnabled(false);
|
| 326 |
+
if (btnCamera != null) btnCamera.setEnabled(false);
|
| 327 |
+
|
| 328 |
// Scaled and compressed image bytes on-device
|
| 329 |
byte[] imageBytes = getScaledAndCompressedImage(imageUri);
|
| 330 |
|
|
|
|
| 334 |
resultText.setText(pest + " (" + (int) (confidence * 100) + "%)");
|
| 335 |
treatmentText.setText(treatment);
|
| 336 |
updateConnectionStatus(true, "Connected");
|
| 337 |
+
|
| 338 |
+
// Hide loaders and re-enable buttons
|
| 339 |
+
if (progressBar != null) progressBar.setVisibility(View.GONE);
|
| 340 |
+
if (loadingOverlay != null) loadingOverlay.setVisibility(View.GONE);
|
| 341 |
+
if (btnUpload != null) btnUpload.setEnabled(true);
|
| 342 |
+
if (btnSelect != null) btnSelect.setEnabled(true);
|
| 343 |
+
if (btnCamera != null) btnCamera.setEnabled(true);
|
| 344 |
+
|
| 345 |
+
// Auto-scroll layout to reveal results
|
| 346 |
+
if (mainScrollView != null) {
|
| 347 |
+
mainScrollView.post(() -> mainScrollView.fullScroll(View.FOCUS_DOWN));
|
| 348 |
+
}
|
| 349 |
}
|
| 350 |
|
| 351 |
@Override
|
|
|
|
| 353 |
resultText.setText("FAILED: " + message);
|
| 354 |
treatmentText.setText("Check server URL in Connection Settings");
|
| 355 |
updateConnectionStatus(false, message);
|
| 356 |
+
|
| 357 |
+
// Hide loaders and re-enable buttons
|
| 358 |
+
if (progressBar != null) progressBar.setVisibility(View.GONE);
|
| 359 |
+
if (loadingOverlay != null) loadingOverlay.setVisibility(View.GONE);
|
| 360 |
+
if (btnUpload != null) btnUpload.setEnabled(true);
|
| 361 |
+
if (btnSelect != null) btnSelect.setEnabled(true);
|
| 362 |
+
if (btnCamera != null) btnCamera.setEnabled(true);
|
| 363 |
+
|
| 364 |
android.util.Log.e("PestScanner", "Connection Error to " + serverUrl + ": " + message);
|
| 365 |
}
|
| 366 |
});
|
|
|
|
| 368 |
} catch (Exception e) {
|
| 369 |
e.printStackTrace();
|
| 370 |
resultText.setText("Error: " + e.getMessage());
|
| 371 |
+
|
| 372 |
+
// Reset loaders and re-enable buttons on error
|
| 373 |
+
if (progressBar != null) progressBar.setVisibility(View.GONE);
|
| 374 |
+
if (loadingOverlay != null) loadingOverlay.setVisibility(View.GONE);
|
| 375 |
+
if (btnUpload != null) btnUpload.setEnabled(true);
|
| 376 |
+
if (btnSelect != null) btnSelect.setEnabled(true);
|
| 377 |
+
if (btnCamera != null) btnCamera.setEnabled(true);
|
| 378 |
}
|
| 379 |
}
|
| 380 |
|
app/src/main/res/layout/activity_main.xml
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
xmlns:android="http://schemas.android.com/apk/res/android"
|
| 4 |
xmlns:app="http://schemas.android.com/apk/res-auto"
|
| 5 |
xmlns:tools="http://schemas.android.com/tools"
|
|
|
|
| 6 |
android:layout_width="match_parent"
|
| 7 |
android:layout_height="match_parent"
|
| 8 |
android:background="@color/bg_soft_white"
|
|
@@ -98,7 +99,8 @@
|
|
| 98 |
android:orientation="horizontal"
|
| 99 |
android:gravity="center_vertical"
|
| 100 |
android:clickable="true"
|
| 101 |
-
android:focusable="true"
|
|
|
|
| 102 |
|
| 103 |
<ImageView
|
| 104 |
android:layout_width="20dp"
|
|
@@ -196,14 +198,37 @@
|
|
| 196 |
android:id="@+id/imageView"
|
| 197 |
android:layout_width="match_parent"
|
| 198 |
android:layout_height="match_parent"
|
| 199 |
-
android:scaleType="centerCrop"
|
| 200 |
-
android:src="@mipmap/ic_launcher" />
|
| 201 |
|
| 202 |
-
<!--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
<View
|
|
|
|
| 204 |
android:layout_width="match_parent"
|
| 205 |
android:layout_height="match_parent"
|
| 206 |
-
android:background="#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
</com.google.android.material.card.MaterialCardView>
|
| 208 |
|
| 209 |
<!-- Action Buttons Section -->
|
|
|
|
| 3 |
xmlns:android="http://schemas.android.com/apk/res/android"
|
| 4 |
xmlns:app="http://schemas.android.com/apk/res-auto"
|
| 5 |
xmlns:tools="http://schemas.android.com/tools"
|
| 6 |
+
android:id="@+id/mainScrollView"
|
| 7 |
android:layout_width="match_parent"
|
| 8 |
android:layout_height="match_parent"
|
| 9 |
android:background="@color/bg_soft_white"
|
|
|
|
| 99 |
android:orientation="horizontal"
|
| 100 |
android:gravity="center_vertical"
|
| 101 |
android:clickable="true"
|
| 102 |
+
android:focusable="true"
|
| 103 |
+
android:background="?android:attr/selectableItemBackground">
|
| 104 |
|
| 105 |
<ImageView
|
| 106 |
android:layout_width="20dp"
|
|
|
|
| 198 |
android:id="@+id/imageView"
|
| 199 |
android:layout_width="match_parent"
|
| 200 |
android:layout_height="match_parent"
|
| 201 |
+
android:scaleType="centerCrop" />
|
|
|
|
| 202 |
|
| 203 |
+
<!-- Placeholder text when no image is selected -->
|
| 204 |
+
<TextView
|
| 205 |
+
android:id="@+id/tvImagePlaceholder"
|
| 206 |
+
android:layout_width="wrap_content"
|
| 207 |
+
android:layout_height="wrap_content"
|
| 208 |
+
android:layout_gravity="center"
|
| 209 |
+
android:gravity="center"
|
| 210 |
+
android:text="No crop image selected\nTap Gallery or Camera below"
|
| 211 |
+
android:textColor="@color/text_hint"
|
| 212 |
+
android:textSize="14sp"
|
| 213 |
+
android:textStyle="bold"
|
| 214 |
+
android:padding="24dp" />
|
| 215 |
+
|
| 216 |
+
<!-- Semi-transparent overlay to dim image when analyzing -->
|
| 217 |
<View
|
| 218 |
+
android:id="@+id/loadingOverlay"
|
| 219 |
android:layout_width="match_parent"
|
| 220 |
android:layout_height="match_parent"
|
| 221 |
+
android:background="#99000000"
|
| 222 |
+
android:visibility="gone" />
|
| 223 |
+
|
| 224 |
+
<!-- Circular Loading Spinner -->
|
| 225 |
+
<ProgressBar
|
| 226 |
+
android:id="@+id/progressBar"
|
| 227 |
+
android:layout_width="wrap_content"
|
| 228 |
+
android:layout_height="wrap_content"
|
| 229 |
+
android:layout_gravity="center"
|
| 230 |
+
android:visibility="gone"
|
| 231 |
+
android:indeterminateTint="@color/primary_green" />
|
| 232 |
</com.google.android.material.card.MaterialCardView>
|
| 233 |
|
| 234 |
<!-- Action Buttons Section -->
|