From 2e4d02b503882b26b11bde59825aced18910b684 Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Tue, 15 Apr 2025 12:36:18 -0400 Subject: [PATCH] cleaning up UI --- README.md | 18 +- app/src/main/AndroidManifest.xml | 2 +- .../service/FileUploadForegroundService.java | 2 +- .../simplelogupload/view/MainActivity.java | 212 +++++++++++++----- app/src/main/res/drawable/ic_app_icon.xml | 25 +++ app/src/main/res/drawable/ic_notification.xml | 12 + app/src/main/res/drawable/logupload.xml | 48 ++-- app/src/main/res/layout/activity_main.xml | 144 ++++++++++-- app/src/main/res/menu/main_menu.xml | 4 +- app/src/main/res/values/strings.xml | 40 ++++ .../simplelogupload/MainActivityTest.java | 82 +++++++ 11 files changed, 495 insertions(+), 94 deletions(-) create mode 100644 app/src/main/res/drawable/ic_app_icon.xml create mode 100644 app/src/main/res/drawable/ic_notification.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/test/java/com/mattintech/simplelogupload/MainActivityTest.java diff --git a/README.md b/README.md index 2ba8a80..4daaaee 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,32 @@ # SimpleFileUpload -Android client written in java to easily upload Samsung dumpstate files. + +Android client written in Java to easily upload files, including Samsung dumpstate files. Note - this requires that you leverage SimpleFileUpload-Server ## Features -- Upload Samsung dumpstate files or any file via UI or share intent +- General file upload capability from device storage +- Support for Samsung dumpstate files as a specialized feature +- Automatic dumpstate file detection - Automatically compress files to .zip format before upload +- Modern Material Design interface - View upload history with received codes - Copy codes for easy sharing - Support for Android 14+ with proper foreground service implementation - MVC architecture for clean code separation ## Recent Updates +- Redesigned UI with improved user experience +- Added general file upload capability as the main feature +- Dumpstate upload functionality moved to a sub-feature +- Updated app icons for a more modern look - Fixed broadcast receiver registration for Android 13+ - Added proper foreground service type declaration for Android 14+ - Improved reliability of upload completion handling - Enhanced error handling and logging +## Upcoming Features +- Multiple file upload support +- Progress tracking for large files +- Dark mode support +- Upload history filtering and search +- QR code generation for quick sharing diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e2a4e7f..46be7ff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ filePickerLauncher; @Override protected void onCreate(Bundle savedInstanceState) { @@ -59,17 +71,33 @@ public class MainActivity extends AppCompatActivity { return; } + // Set content view setContentView(R.layout.activity_main); uploadController = new UploadController(this); // Initialize UI elements - resultView = findViewById(R.id.resultView); - uploadButton = findViewById(R.id.uploadButton); + dumpstateStatusText = findViewById(R.id.dumpstateStatusText); + selectedFileText = findViewById(R.id.selectedFileText); + selectFileButton = findViewById(R.id.selectFileButton); + uploadDumpstateButton = findViewById(R.id.uploadDumpstateButton); + uploadSelectedFileButton = findViewById(R.id.uploadSelectedFileButton); progressBar = findViewById(R.id.progressBar); + selectedFileCard = findViewById(R.id.selectedFileCard); // Set up button click listeners - uploadButton.setOnClickListener(v -> startUpload()); + selectFileButton.setOnClickListener(v -> openFilePicker()); + uploadDumpstateButton.setOnClickListener(v -> uploadDumpstate()); + uploadSelectedFileButton.setOnClickListener(v -> uploadSelectedFile()); + + // Initialize file picker launcher + filePickerLauncher = registerForActivityResult( + new ActivityResultContracts.GetContent(), + uri -> { + if (uri != null) { + handleFileSelection(uri); + } + }); // Check for necessary permissions checkAndRequestPermissions(); @@ -120,19 +148,14 @@ public class MainActivity extends AppCompatActivity { // For now, just handle the first URI Uri sharedUri = sharedUris.get(0); - resultView.setText("Processing shared file..."); progressBar.setVisibility(View.VISIBLE); - uploadButton.setEnabled(false); // Process the shared URI ShareIntentHandler.processSharedUri(this, sharedUri) .thenAccept(file -> { // Save the file for upload - selectedFile = file; + updateSelectedFile(file); runOnUiThread(() -> { - resultView.setText("File ready: " + file.getName()); - uploadButton.setEnabled(true); - uploadButton.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); }); }) @@ -140,15 +163,113 @@ public class MainActivity extends AppCompatActivity { Log.e(TAG, "Error processing shared file", ex); runOnUiThread(() -> { Toast.makeText(MainActivity.this, - "Error processing file: " + ex.getMessage(), + getString(R.string.file_error, ex.getMessage()), Toast.LENGTH_LONG).show(); - resultView.setText("Error processing file"); progressBar.setVisibility(View.GONE); }); return null; }); } + /** + * Opens the file picker + */ + private void openFilePicker() { + filePickerLauncher.launch("*/*"); + } + + /** + * Handle file selection from file picker + * + * @param uri The URI of the selected file + */ + private void handleFileSelection(Uri uri) { + progressBar.setVisibility(View.VISIBLE); + + // Process the URI + ShareIntentHandler.processSharedUri(this, uri) + .thenAccept(file -> { + // Save the file for upload + updateSelectedFile(file); + runOnUiThread(() -> { + progressBar.setVisibility(View.GONE); + }); + }) + .exceptionally(ex -> { + Log.e(TAG, "Error processing selected file", ex); + runOnUiThread(() -> { + Toast.makeText(MainActivity.this, + getString(R.string.file_error, ex.getMessage()), + Toast.LENGTH_LONG).show(); + progressBar.setVisibility(View.GONE); + }); + return null; + }); + } + + /** + * Upload the selected file + */ + private void uploadSelectedFile() { + if (selectedFile == null || !selectedFile.exists()) { + Toast.makeText(this, R.string.no_file_selected, Toast.LENGTH_SHORT).show(); + return; + } + + // Update UI + progressBar.setVisibility(View.VISIBLE); + uploadSelectedFileButton.setEnabled(false); + selectedFileText.setText(getString(R.string.uploading_file, selectedFile.getName())); + + // Start the upload service + startUploadService(selectedFile); + } + + /** + * Upload the dumpstate file + */ + private void uploadDumpstate() { + if (dumpstateFile == null || !dumpstateFile.exists()) { + Toast.makeText(this, R.string.no_dumpstate_found, Toast.LENGTH_SHORT).show(); + return; + } + + // Update UI + progressBar.setVisibility(View.VISIBLE); + uploadDumpstateButton.setEnabled(false); + dumpstateStatusText.setText(getString(R.string.uploading_file, dumpstateFile.getName())); + + // Start the upload service + startUploadService(dumpstateFile); + } + + /** + * Start the upload service with the given file + * + * @param file The file to upload + */ + private void startUploadService(File file) { + // Start the upload service + Intent serviceIntent = new Intent(this, FileUploadForegroundService.class); + serviceIntent.putExtra(FileUploadForegroundService.EXTRA_FILE_PATH, file.getAbsolutePath()); + startService(serviceIntent); + } + + /** + * Update the selected file UI + * + * @param file The selected file + */ + private void updateSelectedFile(File file) { + selectedFile = file; + runOnUiThread(() -> { + selectedFileCard.setVisibility(View.VISIBLE); + selectedFileText.setText(getString(R.string.selected_file, file.getName())); + uploadSelectedFileButton.setVisibility(View.VISIBLE); + uploadSelectedFileButton.setEnabled(true); + }); + } + /** * Set up the broadcast receiver for upload results */ @@ -158,7 +279,8 @@ public class MainActivity extends AppCompatActivity { public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received broadcast: " + intent.getAction()); progressBar.setVisibility(View.GONE); - uploadButton.setEnabled(true); + uploadSelectedFileButton.setEnabled(true); + uploadDumpstateButton.setEnabled(true); if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_COMPLETE)) { String uploadCode = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_CODE); @@ -167,13 +289,27 @@ public class MainActivity extends AppCompatActivity { } else if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_ERROR)) { String errorMessage = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_ERROR); Log.e(TAG, "Upload error: " + errorMessage); - Toast.makeText(MainActivity.this, "Upload Error: " + errorMessage, Toast.LENGTH_LONG).show(); - resultView.setText("Upload failed: " + errorMessage); + Toast.makeText(MainActivity.this, + getString(R.string.upload_error, errorMessage), + Toast.LENGTH_LONG).show(); + resetUI(); } } }; } + /** + * Reset the UI after upload completion or error + */ + private void resetUI() { + dumpstateStatusText.setText(dumpstateFile != null ? + getString(R.string.dumpstate_found, dumpstateFile.getName()) : + getString(R.string.no_dumpstate_found)); + if (selectedFile != null) { + selectedFileText.setText(getString(R.string.selected_file, selectedFile.getName())); + } + } + @Override protected void onResume() { super.onResume(); @@ -231,23 +367,17 @@ public class MainActivity extends AppCompatActivity { * Check for dumpstate files and update the UI */ private void checkForDumpStateAndUpdateUI() { - // If we already have a selected file (e.g., from share), use that - if (selectedFile != null && selectedFile.exists()) { - resultView.setText("File selected: " + selectedFile.getName()); - uploadButton.setVisibility(View.VISIBLE); - return; - } - - // Otherwise, look for dumpstate files + // Look for dumpstate files File mostRecentDumpStateFile = FileController.getMostRecentDumpStateFile(); if (mostRecentDumpStateFile != null) { - selectedFile = mostRecentDumpStateFile; - resultView.setText("Dumpstate found: " + mostRecentDumpStateFile.getName()); - uploadButton.setVisibility(View.VISIBLE); + dumpstateFile = mostRecentDumpStateFile; + dumpstateStatusText.setText(getString(R.string.dumpstate_found, mostRecentDumpStateFile.getName())); + uploadDumpstateButton.setEnabled(true); } else { - resultView.setText("No dumpstate file found."); - uploadButton.setVisibility(View.GONE); + dumpstateFile = null; + dumpstateStatusText.setText(R.string.no_dumpstate_found); + uploadDumpstateButton.setEnabled(false); } } @@ -259,31 +389,11 @@ public class MainActivity extends AppCompatActivity { if (PermissionUtil.hasAllFilesAccessPermission()) { checkForDumpStateAndUpdateUI(); } else { - Toast.makeText(this, "Storage permission not granted!", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.permission_not_granted, Toast.LENGTH_SHORT).show(); } } } - /** - * Start the upload process - */ - private void startUpload() { - if (selectedFile == null || !selectedFile.exists()) { - Toast.makeText(this, "No file selected", Toast.LENGTH_SHORT).show(); - return; - } - - // Update UI - progressBar.setVisibility(View.VISIBLE); - uploadButton.setEnabled(false); - resultView.setText("Uploading file..."); - - // Start the upload service - Intent serviceIntent = new Intent(this, FileUploadForegroundService.class); - serviceIntent.putExtra(FileUploadForegroundService.EXTRA_FILE_PATH, selectedFile.getAbsolutePath()); - startService(serviceIntent); - } - /** * Show a dialog with the upload success result * @@ -297,7 +407,7 @@ public class MainActivity extends AppCompatActivity { dialog.show(); } catch (Exception e) { Log.e(TAG, "Error showing upload result dialog", e); - Toast.makeText(this, "Upload successful! Code: " + uploadCode, Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.upload_success, uploadCode), Toast.LENGTH_LONG).show(); } }); } diff --git a/app/src/main/res/drawable/ic_app_icon.xml b/app/src/main/res/drawable/ic_app_icon.xml new file mode 100644 index 0000000..239dfc8 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_icon.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml new file mode 100644 index 0000000..29673be --- /dev/null +++ b/app/src/main/res/drawable/ic_notification.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/drawable/logupload.xml b/app/src/main/res/drawable/logupload.xml index dd45d7c..f060c06 100644 --- a/app/src/main/res/drawable/logupload.xml +++ b/app/src/main/res/drawable/logupload.xml @@ -1,25 +1,35 @@ + - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 29699b8..5ee3e96 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,35 +5,143 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> - -