cleaning up UI
This commit is contained in:
18
README.md
18
README.md
@@ -1,18 +1,32 @@
|
|||||||
# SimpleFileUpload
|
# 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
|
Note - this requires that you leverage SimpleFileUpload-Server
|
||||||
|
|
||||||
## Features
|
## 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
|
- Automatically compress files to .zip format before upload
|
||||||
|
- Modern Material Design interface
|
||||||
- View upload history with received codes
|
- View upload history with received codes
|
||||||
- Copy codes for easy sharing
|
- Copy codes for easy sharing
|
||||||
- Support for Android 14+ with proper foreground service implementation
|
- Support for Android 14+ with proper foreground service implementation
|
||||||
- MVC architecture for clean code separation
|
- MVC architecture for clean code separation
|
||||||
|
|
||||||
## Recent Updates
|
## 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+
|
- Fixed broadcast receiver registration for Android 13+
|
||||||
- Added proper foreground service type declaration for Android 14+
|
- Added proper foreground service type declaration for Android 14+
|
||||||
- Improved reliability of upload completion handling
|
- Improved reliability of upload completion handling
|
||||||
- Enhanced error handling and logging
|
- 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
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@android:drawable/ic_menu_upload"
|
android:icon="@drawable/ic_app_icon"
|
||||||
android:label="SimpleLogUpload"
|
android:label="SimpleLogUpload"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public class FileUploadForegroundService extends Service {
|
|||||||
notificationBuilder = new NotificationCompat.Builder(this, AppConstants.NOTIFICATION_CHANNEL_ID)
|
notificationBuilder = new NotificationCompat.Builder(this, AppConstants.NOTIFICATION_CHANNEL_ID)
|
||||||
.setContentTitle("File Upload")
|
.setContentTitle("File Upload")
|
||||||
.setContentText("Preparing file...")
|
.setContentText("Preparing file...")
|
||||||
.setSmallIcon(R.drawable.logupload)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setProgress(100, 0, true)
|
.setProgress(100, 0, true)
|
||||||
.setOngoing(true);
|
.setOngoing(true);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.mattintech.simplelogupload.view;
|
package com.mattintech.simplelogupload.view;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
@@ -36,16 +39,25 @@ import java.util.List;
|
|||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private static final String TAG = AppConstants.APP_TAG + "MainActivity";
|
private static final String TAG = AppConstants.APP_TAG + "MainActivity";
|
||||||
|
|
||||||
private TextView resultView;
|
private TextView dumpstateStatusText;
|
||||||
private Button uploadButton;
|
private TextView selectedFileText;
|
||||||
|
private Button selectFileButton;
|
||||||
|
private Button uploadDumpstateButton;
|
||||||
|
private Button uploadSelectedFileButton;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
private CardView selectedFileCard;
|
||||||
|
|
||||||
private File selectedFile;
|
private File selectedFile;
|
||||||
|
private File dumpstateFile;
|
||||||
|
|
||||||
private UploadController uploadController;
|
private UploadController uploadController;
|
||||||
|
|
||||||
// BroadcastReceiver to listen for upload results
|
// BroadcastReceiver to listen for upload results
|
||||||
private BroadcastReceiver uploadReceiver;
|
private BroadcastReceiver uploadReceiver;
|
||||||
|
|
||||||
|
// Activity result launcher for file picking
|
||||||
|
private ActivityResultLauncher<String> filePickerLauncher;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -59,17 +71,33 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set content view
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
uploadController = new UploadController(this);
|
uploadController = new UploadController(this);
|
||||||
|
|
||||||
// Initialize UI elements
|
// Initialize UI elements
|
||||||
resultView = findViewById(R.id.resultView);
|
dumpstateStatusText = findViewById(R.id.dumpstateStatusText);
|
||||||
uploadButton = findViewById(R.id.uploadButton);
|
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);
|
progressBar = findViewById(R.id.progressBar);
|
||||||
|
selectedFileCard = findViewById(R.id.selectedFileCard);
|
||||||
|
|
||||||
// Set up button click listeners
|
// 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
|
// Check for necessary permissions
|
||||||
checkAndRequestPermissions();
|
checkAndRequestPermissions();
|
||||||
@@ -120,19 +148,14 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
// For now, just handle the first URI
|
// For now, just handle the first URI
|
||||||
Uri sharedUri = sharedUris.get(0);
|
Uri sharedUri = sharedUris.get(0);
|
||||||
|
|
||||||
resultView.setText("Processing shared file...");
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
uploadButton.setEnabled(false);
|
|
||||||
|
|
||||||
// Process the shared URI
|
// Process the shared URI
|
||||||
ShareIntentHandler.processSharedUri(this, sharedUri)
|
ShareIntentHandler.processSharedUri(this, sharedUri)
|
||||||
.thenAccept(file -> {
|
.thenAccept(file -> {
|
||||||
// Save the file for upload
|
// Save the file for upload
|
||||||
selectedFile = file;
|
updateSelectedFile(file);
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
resultView.setText("File ready: " + file.getName());
|
|
||||||
uploadButton.setEnabled(true);
|
|
||||||
uploadButton.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -140,15 +163,113 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
Log.e(TAG, "Error processing shared file", ex);
|
Log.e(TAG, "Error processing shared file", ex);
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
Toast.makeText(MainActivity.this,
|
Toast.makeText(MainActivity.this,
|
||||||
"Error processing file: " + ex.getMessage(),
|
getString(R.string.file_error, ex.getMessage()),
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
resultView.setText("Error processing file");
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
});
|
});
|
||||||
return null;
|
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
|
* Set up the broadcast receiver for upload results
|
||||||
*/
|
*/
|
||||||
@@ -158,7 +279,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Log.d(TAG, "Received broadcast: " + intent.getAction());
|
Log.d(TAG, "Received broadcast: " + intent.getAction());
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
uploadButton.setEnabled(true);
|
uploadSelectedFileButton.setEnabled(true);
|
||||||
|
uploadDumpstateButton.setEnabled(true);
|
||||||
|
|
||||||
if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_COMPLETE)) {
|
if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_COMPLETE)) {
|
||||||
String uploadCode = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_CODE);
|
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)) {
|
} else if (intent.getAction().equals(AppConstants.ACTION_UPLOAD_ERROR)) {
|
||||||
String errorMessage = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_ERROR);
|
String errorMessage = intent.getStringExtra(AppConstants.EXTRA_UPLOAD_ERROR);
|
||||||
Log.e(TAG, "Upload error: " + errorMessage);
|
Log.e(TAG, "Upload error: " + errorMessage);
|
||||||
Toast.makeText(MainActivity.this, "Upload Error: " + errorMessage, Toast.LENGTH_LONG).show();
|
Toast.makeText(MainActivity.this,
|
||||||
resultView.setText("Upload failed: " + errorMessage);
|
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
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -231,23 +367,17 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
* Check for dumpstate files and update the UI
|
* Check for dumpstate files and update the UI
|
||||||
*/
|
*/
|
||||||
private void checkForDumpStateAndUpdateUI() {
|
private void checkForDumpStateAndUpdateUI() {
|
||||||
// If we already have a selected file (e.g., from share), use that
|
// Look for dumpstate files
|
||||||
if (selectedFile != null && selectedFile.exists()) {
|
|
||||||
resultView.setText("File selected: " + selectedFile.getName());
|
|
||||||
uploadButton.setVisibility(View.VISIBLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, look for dumpstate files
|
|
||||||
File mostRecentDumpStateFile = FileController.getMostRecentDumpStateFile();
|
File mostRecentDumpStateFile = FileController.getMostRecentDumpStateFile();
|
||||||
|
|
||||||
if (mostRecentDumpStateFile != null) {
|
if (mostRecentDumpStateFile != null) {
|
||||||
selectedFile = mostRecentDumpStateFile;
|
dumpstateFile = mostRecentDumpStateFile;
|
||||||
resultView.setText("Dumpstate found: " + mostRecentDumpStateFile.getName());
|
dumpstateStatusText.setText(getString(R.string.dumpstate_found, mostRecentDumpStateFile.getName()));
|
||||||
uploadButton.setVisibility(View.VISIBLE);
|
uploadDumpstateButton.setEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
resultView.setText("No dumpstate file found.");
|
dumpstateFile = null;
|
||||||
uploadButton.setVisibility(View.GONE);
|
dumpstateStatusText.setText(R.string.no_dumpstate_found);
|
||||||
|
uploadDumpstateButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,31 +389,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (PermissionUtil.hasAllFilesAccessPermission()) {
|
if (PermissionUtil.hasAllFilesAccessPermission()) {
|
||||||
checkForDumpStateAndUpdateUI();
|
checkForDumpStateAndUpdateUI();
|
||||||
} else {
|
} 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
|
* Show a dialog with the upload success result
|
||||||
*
|
*
|
||||||
@@ -297,7 +407,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error showing upload result dialog", 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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
25
app/src/main/res/drawable/ic_app_icon.xml
Normal file
25
app/src/main/res/drawable/ic_app_icon.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<!-- Background circle -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#4285F4"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2z"/>
|
||||||
|
|
||||||
|
<!-- Cloud with upload arrow -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M16.5,10h-1.5v-1.5c0,-1.66 -1.34,-3 -3,-3s-3,1.34 -3,3v1.5h-1.5c-0.83,0 -1.5,0.67 -1.5,1.5v5c0,0.83 0.67,1.5 1.5,1.5h9c0.83,0 1.5,-0.67 1.5,-1.5v-5c0,-0.83 -0.67,-1.5 -1.5,-1.5zM12,17c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1zM14,11h-4v-2.5c0,-1.1 0.9,-2 2,-2s2,0.9 2,2v2.5z"/>
|
||||||
|
|
||||||
|
<!-- Upload arrow -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M12,14L12,14c-0.3,0 -0.5,-0.2 -0.5,-0.5v-3c0,-0.3 0.2,-0.5 0.5,-0.5l0,0c0.3,0 0.5,0.2 0.5,0.5v3c0,0.3 -0.2,0.5 -0.5,0.5z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M13.5,12.5l-1.5,-1.5l-1.5,1.5"/>
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_notification.xml
Normal file
12
app/src/main/res/drawable/ic_notification.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<!-- Simple upload icon (outline only for notification icons) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M9,16h6v-6h4l-7,-7l-7,7h4V16zM5,18h14v2H5V18z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,25 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="100dp"
|
android:width="100dp"
|
||||||
android:height="100dp"
|
android:height="100dp"
|
||||||
android:viewportWidth="100"
|
android:viewportWidth="100"
|
||||||
android:viewportHeight="100">
|
android:viewportHeight="100">
|
||||||
|
|
||||||
|
<!-- Background circle -->
|
||||||
<path
|
<path
|
||||||
android:pathData="M50,50m-45,0a45,45 0,1 1,90 0a45,45 0,1 1,-90 0"
|
android:pathData="M50,50m-45,0a45,45 0,1 1,90 0a45,45 0,1 1,-90 0"
|
||||||
android:strokeWidth="5"
|
android:fillColor="#4285F4"/>
|
||||||
android:fillColor="#f0f0f0"
|
|
||||||
android:strokeColor="#bbb"/>
|
<!-- File with upload arrow -->
|
||||||
<path
|
<path
|
||||||
android:pathData="M30,30h40v50h-40z"
|
android:pathData="M32,30L54,30L68,44L68,70L32,70Z"
|
||||||
android:strokeWidth="2"
|
android:fillColor="#FFFFFF"/>
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:strokeColor="#333"/>
|
<!-- Folded corner -->
|
||||||
<path
|
<path
|
||||||
android:pathData="M30,30l15,0l0,15l-15,0z"
|
android:pathData="M54,30L54,44L68,44Z"
|
||||||
android:fillColor="#bbb"/>
|
android:fillColor="#E1E1E1"/>
|
||||||
|
|
||||||
|
<!-- Upload arrow -->
|
||||||
<path
|
<path
|
||||||
android:pathData="M50,40l-10,10l5,0l0,10l10,0l0,-10l5,0z"
|
android:pathData="M50,50m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
|
||||||
android:fillColor="#4a90e2"/>
|
android:fillColor="#34A853"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="M45,60h10v15h-10z"
|
android:pathData="M50,44L50,56"
|
||||||
android:fillColor="#4a90e2"/>
|
android:strokeWidth="3"
|
||||||
|
android:strokeColor="#FFFFFF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M45,49L50,44L55,49"
|
||||||
|
android:strokeWidth="3"
|
||||||
|
android:strokeColor="#FFFFFF"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -5,35 +5,143 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/resultView"
|
android:id="@+id/headerText"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Looking for dumpstate..."
|
android:text="@string/app_header"
|
||||||
android:textSize="18sp"
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"/>
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"/>
|
|
||||||
|
|
||||||
<Button
|
<!-- Main Upload Options Section -->
|
||||||
android:id="@+id/uploadButton"
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/uploadOptionsCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/headerText">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Upload Dumpstate"
|
android:text="@string/upload_options_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/selectFileButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/select_file_button"
|
||||||
|
android:drawableStart="@android:drawable/ic_menu_search"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/uploadDumpstateButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/upload_dumpstate_button"
|
||||||
|
android:drawableStart="@android:drawable/ic_menu_save"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Dumpstate Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/dumpstateCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/uploadOptionsCard">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dumpstate_section_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dumpstateStatusText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/looking_for_dumpstate"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Selected File Section -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/selectedFileCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toBottomOf="@id/resultView"
|
app:layout_constraintTop_toBottomOf="@id/dumpstateCard">
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
<LinearLayout
|
||||||
android:layout_marginTop="16dp"/>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/selected_file_section_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/selectedFileText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/no_file_selected"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/uploadSelectedFileButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/upload_selected_file"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toBottomOf="@id/uploadButton"
|
app:layout_constraintTop_toBottomOf="@id/selectedFileCard"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginTop="16dp"/>
|
android:layout_marginTop="16dp"/>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_history"
|
android:id="@+id/action_history"
|
||||||
android:title="Upload History"
|
android:title="@string/menu_history"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:showAsAction="never" />
|
android:showAsAction="never" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
android:title="Settings"
|
android:title="@string/menu_settings"
|
||||||
android:orderInCategory="200"
|
android:orderInCategory="200"
|
||||||
android:showAsAction="never" />
|
android:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
40
app/src/main/res/values/strings.xml
Normal file
40
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">SimpleLogUpload</string>
|
||||||
|
<string name="title_activity_main">SimpleLogUpload</string>
|
||||||
|
|
||||||
|
<!-- Main UI -->
|
||||||
|
<string name="app_header">Upload Files</string>
|
||||||
|
<string name="select_file_button">Select File from Storage</string>
|
||||||
|
<string name="upload_dumpstate_button">Upload Samsung Dumpstate</string>
|
||||||
|
<string name="upload_selected_file">Upload File</string>
|
||||||
|
<string name="no_file_selected">No file selected</string>
|
||||||
|
<string name="looking_for_dumpstate">Looking for dumpstate…</string>
|
||||||
|
<string name="no_dumpstate_found">No dumpstate file found</string>
|
||||||
|
<string name="dumpstate_found">Dumpstate found: %1$s</string>
|
||||||
|
<string name="selected_file">File: %1$s</string>
|
||||||
|
<string name="uploading_file">Uploading: %1$s</string>
|
||||||
|
<string name="upload_success">Upload successful! Code: %1$s</string>
|
||||||
|
<string name="upload_error">Upload error: %1$s</string>
|
||||||
|
<string name="file_error">Error processing file: %1$s</string>
|
||||||
|
|
||||||
|
<!-- Upload options section -->
|
||||||
|
<string name="upload_options_title">Select Upload Option</string>
|
||||||
|
<string name="dumpstate_section_title">Dumpstate Detection</string>
|
||||||
|
<string name="selected_file_section_title">Selected File</string>
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<string name="menu_history">History</string>
|
||||||
|
<string name="menu_settings">Settings</string>
|
||||||
|
|
||||||
|
<!-- Upload Dialog -->
|
||||||
|
<string name="dialog_upload_success_title">Upload Successful</string>
|
||||||
|
<string name="dialog_upload_code">Your upload code is:</string>
|
||||||
|
<string name="dialog_copy_code">Copy Code</string>
|
||||||
|
<string name="dialog_close">Close</string>
|
||||||
|
<string name="code_copied">Code copied to clipboard</string>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
<string name="permission_storage_required">Storage permission is required</string>
|
||||||
|
<string name="permission_not_granted">Permission not granted!</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.mattintech.simplelogupload;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.test.core.app.ActivityScenario;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.mattintech.simplelogupload.view.MainActivity;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for MainActivity
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@Config(sdk = 30)
|
||||||
|
public class MainActivityTest {
|
||||||
|
|
||||||
|
private ActivityScenario<MainActivity> activityScenario;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
activityScenario = ActivityScenario.launch(MainActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
if (activityScenario != null) {
|
||||||
|
activityScenario.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainActivityUIComponents() {
|
||||||
|
activityScenario.onActivity(activity -> {
|
||||||
|
// Verify UI components exist
|
||||||
|
assertNotNull(activity.findViewById(R.id.headerText));
|
||||||
|
assertNotNull(activity.findViewById(R.id.uploadOptionsCard));
|
||||||
|
assertNotNull(activity.findViewById(R.id.dumpstateCard));
|
||||||
|
assertNotNull(activity.findViewById(R.id.selectFileButton));
|
||||||
|
assertNotNull(activity.findViewById(R.id.uploadDumpstateButton));
|
||||||
|
|
||||||
|
// Verify initial state
|
||||||
|
TextView dumpstateStatusText = activity.findViewById(R.id.dumpstateStatusText);
|
||||||
|
assertNotNull(dumpstateStatusText);
|
||||||
|
|
||||||
|
// Selected file card should be hidden initially
|
||||||
|
CardView selectedFileCard = activity.findViewById(R.id.selectedFileCard);
|
||||||
|
assertEquals(View.GONE, selectedFileCard.getVisibility());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFileSelectionUI() {
|
||||||
|
activityScenario.onActivity(activity -> {
|
||||||
|
// Simulate file selection
|
||||||
|
TextView selectedFileText = activity.findViewById(R.id.selectedFileText);
|
||||||
|
Button uploadSelectedFileButton = activity.findViewById(R.id.uploadSelectedFileButton);
|
||||||
|
CardView selectedFileCard = activity.findViewById(R.id.selectedFileCard);
|
||||||
|
|
||||||
|
// Initially hidden
|
||||||
|
assertEquals(View.GONE, selectedFileCard.getVisibility());
|
||||||
|
assertEquals(View.GONE, uploadSelectedFileButton.getVisibility());
|
||||||
|
|
||||||
|
// After selection (would require more complex testing with Intents)
|
||||||
|
// This is just to verify the UI logic
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user