cleaning up UI
This commit is contained in:
18
README.md
18
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
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@android:drawable/ic_menu_upload"
|
||||
android:icon="@drawable/ic_app_icon"
|
||||
android:label="SimpleLogUpload"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -173,7 +173,7 @@ public class FileUploadForegroundService extends Service {
|
||||
notificationBuilder = new NotificationCompat.Builder(this, AppConstants.NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle("File Upload")
|
||||
.setContentText("Preparing file...")
|
||||
.setSmallIcon(R.drawable.logupload)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setProgress(100, 0, true)
|
||||
.setOngoing(true);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.mattintech.simplelogupload.view;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -36,16 +39,25 @@ import java.util.List;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = AppConstants.APP_TAG + "MainActivity";
|
||||
|
||||
private TextView resultView;
|
||||
private Button uploadButton;
|
||||
private TextView dumpstateStatusText;
|
||||
private TextView selectedFileText;
|
||||
private Button selectFileButton;
|
||||
private Button uploadDumpstateButton;
|
||||
private Button uploadSelectedFileButton;
|
||||
private ProgressBar progressBar;
|
||||
private CardView selectedFileCard;
|
||||
|
||||
private File selectedFile;
|
||||
private File dumpstateFile;
|
||||
|
||||
private UploadController uploadController;
|
||||
|
||||
// BroadcastReceiver to listen for upload results
|
||||
private BroadcastReceiver uploadReceiver;
|
||||
|
||||
// Activity result launcher for file picking
|
||||
private ActivityResultLauncher<String> filePickerLauncher;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100">
|
||||
|
||||
<!-- Background circle -->
|
||||
<path
|
||||
android:pathData="M50,50m-45,0a45,45 0,1 1,90 0a45,45 0,1 1,-90 0"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#f0f0f0"
|
||||
android:strokeColor="#bbb"/>
|
||||
android:fillColor="#4285F4"/>
|
||||
|
||||
<!-- File with upload arrow -->
|
||||
<path
|
||||
android:pathData="M30,30h40v50h-40z"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#333"/>
|
||||
android:pathData="M32,30L54,30L68,44L68,70L32,70Z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
|
||||
<!-- Folded corner -->
|
||||
<path
|
||||
android:pathData="M30,30l15,0l0,15l-15,0z"
|
||||
android:fillColor="#bbb"/>
|
||||
android:pathData="M54,30L54,44L68,44Z"
|
||||
android:fillColor="#E1E1E1"/>
|
||||
|
||||
<!-- Upload arrow -->
|
||||
<path
|
||||
android:pathData="M50,40l-10,10l5,0l0,10l10,0l0,-10l5,0z"
|
||||
android:fillColor="#4a90e2"/>
|
||||
android:pathData="M50,50m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
|
||||
android:fillColor="#34A853"/>
|
||||
<path
|
||||
android:pathData="M45,60h10v15h-10z"
|
||||
android:fillColor="#4a90e2"/>
|
||||
android:pathData="M50,44L50,56"
|
||||
android:strokeWidth="3"
|
||||
android:strokeColor="#FFFFFF"/>
|
||||
<path
|
||||
android:pathData="M45,49L50,44L55,49"
|
||||
android:strokeWidth="3"
|
||||
android:strokeColor="#FFFFFF"/>
|
||||
</vector>
|
||||
|
||||
@@ -5,35 +5,143 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/resultView"
|
||||
android:id="@+id/headerText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Looking for dumpstate..."
|
||||
android:textSize="18sp"
|
||||
android:text="@string/app_header"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
android:layout_marginTop="16dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/uploadButton"
|
||||
<!-- Main Upload Options Section -->
|
||||
<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_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"
|
||||
app:layout_constraintTop_toBottomOf="@id/resultView"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/dumpstateCard">
|
||||
|
||||
<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/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
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/uploadButton"
|
||||
app:layout_constraintTop_toBottomOf="@id/selectedFileCard"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp"/>
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_history"
|
||||
android:title="Upload History"
|
||||
android:title="@string/menu_history"
|
||||
android:orderInCategory="100"
|
||||
android:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:title="Settings"
|
||||
android:title="@string/menu_settings"
|
||||
android:orderInCategory="200"
|
||||
android:showAsAction="never" />
|
||||
</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