From e36d0616d56332ddf04fc30df132e1612e5b37d1 Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Tue, 15 Apr 2025 09:52:20 -0400 Subject: [PATCH] adding settings screen --- MOBILE.md | 205 ------------------ app/src/main/AndroidManifest.xml | 11 + .../constant/NetworkConstants.java | 8 +- .../controller/UploadController.java | 25 ++- .../simplelogupload/util/PreferencesUtil.java | 116 ++++++++++ .../simplelogupload/view/MainActivity.java | 4 + .../view/SettingsActivity.java | 128 +++++++++++ app/src/main/res/layout/activity_settings.xml | 86 ++++++++ app/src/main/res/layout/activity_welcome.xml | 64 ++++++ app/src/main/res/menu/main_menu.xml | 5 + 10 files changed, 442 insertions(+), 210 deletions(-) delete mode 100644 MOBILE.md create mode 100644 app/src/main/java/com/mattintech/simplelogupload/util/PreferencesUtil.java create mode 100644 app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java create mode 100644 app/src/main/res/layout/activity_settings.xml create mode 100644 app/src/main/res/layout/activity_welcome.xml diff --git a/MOBILE.md b/MOBILE.md deleted file mode 100644 index 0029d7a..0000000 --- a/MOBILE.md +++ /dev/null @@ -1,205 +0,0 @@ -# Mobile Integration Guide - -This document outlines the requirements for mobile applications to integrate with the Simple File Upload Server. - -## API Endpoints - -### Upload File -**Endpoint:** `POST /upload` -**Content-Type:** `multipart/form-data` - -**Request Parameters:** -``` -file: File (required) - The file to upload -max_downloads: Integer (optional) - Maximum number of downloads allowed (default: 1) -expiry_hours: Integer (optional) - Hours until file expires (default: 24, max: 24) -``` - -**Example Request (Swift):** -```swift -let url = URL(string: "http://your-server:7777/upload")! -var request = URLRequest(url: url) -request.httpMethod = "POST" - -let boundary = "Boundary-\(UUID().uuidString)" -request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - -var body = Data() -// Add file data -body.append("--\(boundary)\r\n") -body.append("Content-Disposition: form-data; name=\"file\"; filename=\"log.txt\"\r\n") -body.append("Content-Type: text/plain\r\n\r\n") -body.append(fileData) -body.append("\r\n") -// Add parameters -body.append("--\(boundary)\r\n") -body.append("Content-Disposition: form-data; name=\"max_downloads\"\r\n\r\n") -body.append("3\r\n") -body.append("--\(boundary)\r\n") -body.append("Content-Disposition: form-data; name=\"expiry_hours\"\r\n\r\n") -body.append("24\r\n") -body.append("--\(boundary)--\r\n") - -request.httpBody = body -``` - -**Example Request (Kotlin):** -```kotlin -val file = File("log.txt") -val requestBody = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart( - "file", - file.name, - file.asRequestBody("application/octet-stream".toMediaType()) - ) - .addFormDataPart("max_downloads", "3") - .addFormDataPart("expiry_hours", "24") - .build() - -val request = Request.Builder() - .url("http://your-server:7777/upload") - .post(requestBody) - .build() -``` - -**Success Response:** -```json -{ - "code": "ABC123" -} -``` - -**Error Responses:** -```json -{ - "error": "No file part" -} -``` -Status: 400 Bad Request - -```json -{ - "error": "No selected file" -} -``` -Status: 400 Bad Request - -### Download File -**Endpoint:** `GET /download/{code}` - -**Parameters:** -- `code`: 6-character alphanumeric code received from upload - -**Example Request (Swift):** -```swift -let code = "ABC123" -let url = URL(string: "http://your-server:7777/download/\(code)")! -let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in - guard let localURL = localURL else { return } - // Handle downloaded file -} -task.resume() -``` - -**Example Request (Kotlin):** -```kotlin -val code = "ABC123" -val request = Request.Builder() - .url("http://your-server:7777/download/$code") - .get() - .build() - -client.newCall(request).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - // Handle downloaded file - } - override fun onFailure(call: Call, e: IOException) { - // Handle error - } -}) -``` - -**Success Response:** -- Status: 200 OK -- Content-Type: application/octet-stream -- Content-Disposition: attachment; filename="original_filename.ext" - -**Error Responses:** -- Status: 404 Not Found -- Content-Type: application/json - -Various error messages: -```json -{ - "error": "Invalid or expired code" -} -``` -```json -{ - "error": "File expired" -} -``` -```json -{ - "error": "Download limit reached" -} -``` -```json -{ - "error": "File not found" -} -``` - -## Implementation Requirements - -### File Upload -1. Implement proper error handling for network issues -2. Show upload progress to user -3. Handle timeout scenarios (recommended timeout: 60 seconds) -4. Store received code securely if needed -5. Validate file size before upload (max size: 16MB) -6. Implement retry logic for failed uploads - -### File Download -1. Handle all error scenarios gracefully -2. Show download progress to user -3. Validate downloaded file integrity -4. Implement proper file saving logic -5. Handle interrupted downloads with resume capability if possible - -### Security Considerations -1. Use HTTPS for all API calls in production -2. Don't store sensitive files for extended periods -3. Implement proper error messages without exposing server details -4. Sanitize all user inputs -5. Handle session timeouts appropriately - -## Testing Requirements -1. Test with various file types and sizes -2. Verify all error scenarios -3. Test network condition variations: - - Slow network - - Intermittent connectivity - - Network switches (WiFi to Cellular) -4. Test concurrent uploads/downloads -5. Verify proper cleanup of temporary files - -## Sample Status Codes -- 200: Success -- 400: Bad Request (invalid parameters) -- 404: Not Found (invalid code or expired file) -- 413: Payload Too Large (file size exceeded) -- 500: Server Error - -## Rate Limiting -- Maximum 10 requests per minute per IP -- Implement exponential backoff for retries - -## Migration Notes -If upgrading from previous versions: -1. Update API endpoint URLs -2. Implement new error handling -3. Update file size limits -4. Add support for download codes -5. Remove old URL-based download logic \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a05985e..1d59082 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".view.MainActivity" /> + + + + + saveSettings()); + resetButton.setOnClickListener(v -> resetSettings()); + } + + /** + * Load current settings from SharedPreferences + */ + private void loadSettings() { + serverAddressInput.setText(PreferencesUtil.getServerAddress(this)); + serverPortInput.setText(String.valueOf(PreferencesUtil.getServerPort(this))); + clientKeyInput.setText(PreferencesUtil.getClientKey(this)); + } + + /** + * Save settings to SharedPreferences + */ + private void saveSettings() { + try { + // Get values from input fields + String serverAddress = serverAddressInput.getText().toString().trim(); + String serverPortStr = serverPortInput.getText().toString().trim(); + String clientKey = clientKeyInput.getText().toString().trim(); + + // Validate input + if (TextUtils.isEmpty(serverAddress)) { + serverAddressInput.setError("Server address is required"); + return; + } + + if (TextUtils.isEmpty(serverPortStr)) { + serverPortInput.setError("Server port is required"); + return; + } + + int serverPort; + try { + serverPort = Integer.parseInt(serverPortStr); + if (serverPort <= 0 || serverPort > 65535) { + serverPortInput.setError("Port must be between 1 and 65535"); + return; + } + } catch (NumberFormatException e) { + serverPortInput.setError("Invalid port number"); + return; + } + + if (TextUtils.isEmpty(clientKey)) { + clientKeyInput.setError("Client key is required"); + return; + } + + // Save settings + PreferencesUtil.setServerAddress(this, serverAddress); + PreferencesUtil.setServerPort(this, serverPort); + PreferencesUtil.setClientKey(this, clientKey); + + Log.d(TAG, "Settings saved successfully"); + Toast.makeText(this, "Settings saved successfully", Toast.LENGTH_SHORT).show(); + finish(); + + } catch (Exception e) { + Log.e(TAG, "Error saving settings", e); + Toast.makeText(this, "Error saving settings: " + e.getMessage(), Toast.LENGTH_LONG).show(); + } + } + + /** + * Reset settings to default values + */ + private void resetSettings() { + PreferencesUtil.resetToDefaults(this); + loadSettings(); + Toast.makeText(this, "Settings reset to defaults", Toast.LENGTH_SHORT).show(); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } +} diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..04658a6 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + +