adding settings screen
This commit is contained in:
205
MOBILE.md
205
MOBILE.md
@@ -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
|
|
||||||
@@ -55,6 +55,17 @@
|
|||||||
android:value=".view.MainActivity" />
|
android:value=".view.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<!-- Settings Activity -->
|
||||||
|
<activity
|
||||||
|
android:name=".view.SettingsActivity"
|
||||||
|
android:label="Settings"
|
||||||
|
android:parentActivityName=".view.MainActivity"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".view.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- File Upload Foreground Service -->
|
<!-- File Upload Foreground Service -->
|
||||||
<service
|
<service
|
||||||
android:name=".service.FileUploadForegroundService"
|
android:name=".service.FileUploadForegroundService"
|
||||||
|
|||||||
@@ -4,14 +4,18 @@ package com.mattintech.simplelogupload.constant;
|
|||||||
* Network-related constants
|
* Network-related constants
|
||||||
*/
|
*/
|
||||||
public class NetworkConstants {
|
public class NetworkConstants {
|
||||||
// Server constants
|
// These URL constants are now dynamically set in getServerUrl()
|
||||||
public static final String SERVER_URL = "http://172.16.100.167:7777/upload";
|
// via PreferencesUtil to allow for customization
|
||||||
|
public static final String SERVER_URL_BASE = "/upload";
|
||||||
|
|
||||||
// Upload parameters
|
// Upload parameters
|
||||||
public static final String PARAM_FILE = "file";
|
public static final String PARAM_FILE = "file";
|
||||||
public static final String PARAM_MAX_DOWNLOADS = "max_downloads";
|
public static final String PARAM_MAX_DOWNLOADS = "max_downloads";
|
||||||
public static final String PARAM_EXPIRY_HOURS = "expiry_hours";
|
public static final String PARAM_EXPIRY_HOURS = "expiry_hours";
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
public static final String CLIENT_KEY_HEADER = "client-key";
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
public static final int DEFAULT_MAX_DOWNLOADS = 1;
|
public static final int DEFAULT_MAX_DOWNLOADS = 1;
|
||||||
public static final int DEFAULT_EXPIRY_HOURS = 24;
|
public static final int DEFAULT_EXPIRY_HOURS = 24;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.mattintech.simplelogupload.db.UploadHistoryRepository;
|
|||||||
import com.mattintech.simplelogupload.model.UploadHistory;
|
import com.mattintech.simplelogupload.model.UploadHistory;
|
||||||
import com.mattintech.simplelogupload.model.UploadResult;
|
import com.mattintech.simplelogupload.model.UploadResult;
|
||||||
import com.mattintech.simplelogupload.service.FileUploadForegroundService;
|
import com.mattintech.simplelogupload.service.FileUploadForegroundService;
|
||||||
|
import com.mattintech.simplelogupload.util.PreferencesUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -103,11 +104,21 @@ public class UploadController {
|
|||||||
.addFormDataPart(NetworkConstants.PARAM_EXPIRY_HOURS, String.valueOf(expiryHours))
|
.addFormDataPart(NetworkConstants.PARAM_EXPIRY_HOURS, String.valueOf(expiryHours))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Get server URL and client key from preferences
|
||||||
|
String serverUrl = PreferencesUtil.getServerUrl(context);
|
||||||
|
String clientKey = PreferencesUtil.getClientKey(context);
|
||||||
|
|
||||||
|
Log.d(TAG, "Using server URL: " + serverUrl);
|
||||||
|
|
||||||
|
// Build request with client-key header
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(NetworkConstants.SERVER_URL)
|
.url(serverUrl)
|
||||||
|
.header(NetworkConstants.CLIENT_KEY_HEADER, clientKey)
|
||||||
.post(requestBody)
|
.post(requestBody)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending upload request with " + NetworkConstants.CLIENT_KEY_HEADER + " header");
|
||||||
|
|
||||||
client.newCall(request).enqueue(new Callback() {
|
client.newCall(request).enqueue(new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call call, IOException e) {
|
public void onFailure(Call call, IOException e) {
|
||||||
@@ -142,8 +153,16 @@ public class UploadController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String errorBody = response.body().string();
|
String errorBody = response.body().string();
|
||||||
String errorMsg = "Upload failed, Response code: " + response.code();
|
String errorMsg;
|
||||||
Log.e(TAG, errorMsg + ", Body: " + errorBody);
|
|
||||||
|
// Check for authentication errors
|
||||||
|
if (response.code() == 401 || response.code() == 403) {
|
||||||
|
errorMsg = "Authentication failed. Please check client key.";
|
||||||
|
Log.e(TAG, "Authentication error: " + response.code() + ", Body: " + errorBody);
|
||||||
|
} else {
|
||||||
|
errorMsg = "Upload failed, Response code: " + response.code();
|
||||||
|
Log.e(TAG, errorMsg + ", Body: " + errorBody);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject errorJson = new JSONObject(errorBody);
|
JSONObject errorJson = new JSONObject(errorBody);
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.mattintech.simplelogupload.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for handling application preferences
|
||||||
|
*/
|
||||||
|
public class PreferencesUtil {
|
||||||
|
private static final String PREFERENCES_NAME = "SimpleLogUploadPrefs";
|
||||||
|
|
||||||
|
// Preference keys
|
||||||
|
public static final String KEY_SERVER_ADDRESS = "server_address";
|
||||||
|
public static final String KEY_SERVER_PORT = "server_port";
|
||||||
|
public static final String KEY_CLIENT_KEY = "client_key";
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
private static final String DEFAULT_SERVER_ADDRESS = "172.16.100.167";
|
||||||
|
private static final int DEFAULT_SERVER_PORT = 7777;
|
||||||
|
private static final String DEFAULT_CLIENT_KEY = "default-secret-key";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shared preferences instance
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @return The SharedPreferences instance
|
||||||
|
*/
|
||||||
|
private static SharedPreferences getPreferences(Context context) {
|
||||||
|
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the server address from preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @return The server address
|
||||||
|
*/
|
||||||
|
public static String getServerAddress(Context context) {
|
||||||
|
return getPreferences(context).getString(KEY_SERVER_ADDRESS, DEFAULT_SERVER_ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the server address in preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @param serverAddress The server address to save
|
||||||
|
*/
|
||||||
|
public static void setServerAddress(Context context, String serverAddress) {
|
||||||
|
getPreferences(context).edit().putString(KEY_SERVER_ADDRESS, serverAddress).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the server port from preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @return The server port
|
||||||
|
*/
|
||||||
|
public static int getServerPort(Context context) {
|
||||||
|
return getPreferences(context).getInt(KEY_SERVER_PORT, DEFAULT_SERVER_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the server port in preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @param serverPort The server port to save
|
||||||
|
*/
|
||||||
|
public static void setServerPort(Context context, int serverPort) {
|
||||||
|
getPreferences(context).edit().putInt(KEY_SERVER_PORT, serverPort).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the client key from preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @return The client key
|
||||||
|
*/
|
||||||
|
public static String getClientKey(Context context) {
|
||||||
|
return getPreferences(context).getString(KEY_CLIENT_KEY, DEFAULT_CLIENT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the client key in preferences
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @param clientKey The client key to save
|
||||||
|
*/
|
||||||
|
public static void setClientKey(Context context, String clientKey) {
|
||||||
|
getPreferences(context).edit().putString(KEY_CLIENT_KEY, clientKey).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the complete server URL (address + port + path)
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
* @return The complete server URL
|
||||||
|
*/
|
||||||
|
public static String getServerUrl(Context context) {
|
||||||
|
String address = getServerAddress(context);
|
||||||
|
int port = getServerPort(context);
|
||||||
|
return "http://" + address + ":" + port + "/upload";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all settings to default values
|
||||||
|
*
|
||||||
|
* @param context The application context
|
||||||
|
*/
|
||||||
|
public static void resetToDefaults(Context context) {
|
||||||
|
SharedPreferences.Editor editor = getPreferences(context).edit();
|
||||||
|
editor.putString(KEY_SERVER_ADDRESS, DEFAULT_SERVER_ADDRESS);
|
||||||
|
editor.putInt(KEY_SERVER_PORT, DEFAULT_SERVER_PORT);
|
||||||
|
editor.putString(KEY_CLIENT_KEY, DEFAULT_CLIENT_KEY);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -305,6 +305,10 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
// Open upload history activity
|
// Open upload history activity
|
||||||
startActivity(new Intent(this, UploadHistoryActivity.class));
|
startActivity(new Intent(this, UploadHistoryActivity.class));
|
||||||
return true;
|
return true;
|
||||||
|
} else if (id == R.id.action_settings) {
|
||||||
|
// Open settings activity
|
||||||
|
startActivity(new Intent(this, SettingsActivity.class));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.mattintech.simplelogupload.view;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
|
import com.mattintech.simplelogupload.R;
|
||||||
|
import com.mattintech.simplelogupload.constant.AppConstants;
|
||||||
|
import com.mattintech.simplelogupload.util.PreferencesUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for application settings
|
||||||
|
*/
|
||||||
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = AppConstants.APP_TAG + "SettingsActivity";
|
||||||
|
|
||||||
|
private TextInputEditText serverAddressInput;
|
||||||
|
private TextInputEditText serverPortInput;
|
||||||
|
private TextInputEditText clientKeyInput;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
|
||||||
|
// Initialize UI components
|
||||||
|
serverAddressInput = findViewById(R.id.serverAddressInput);
|
||||||
|
serverPortInput = findViewById(R.id.serverPortInput);
|
||||||
|
clientKeyInput = findViewById(R.id.clientKeyInput);
|
||||||
|
Button saveButton = findViewById(R.id.saveSettingsButton);
|
||||||
|
Button resetButton = findViewById(R.id.resetSettingsButton);
|
||||||
|
|
||||||
|
// Set up action bar
|
||||||
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setTitle("Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load current settings
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
|
// Set up button click listeners
|
||||||
|
saveButton.setOnClickListener(v -> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
app/src/main/res/layout/activity_settings.xml
Normal file
86
app/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Server Configuration"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="Server Address">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/serverAddressInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="Server Port">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/serverPortInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Authentication"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="Client Key"
|
||||||
|
app:endIconMode="password_toggle">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/clientKeyInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/saveSettingsButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Save Settings"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/resetSettingsButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Reset to Defaults"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless.Colored" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
64
app/src/main/res/layout/activity_welcome.xml
Normal file
64
app/src/main/res/layout/activity_welcome.xml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/logoImage"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:src="@android:drawable/ic_menu_upload"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/welcomeTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="Welcome to SimpleLogUpload"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/logoImage" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/welcomeDescription"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="Before you can use this app, you need to configure it to connect to your SimpleLogUpload server instance."
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/welcomeTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/welcomeDetails"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="The app requires:\n\n• Server Address - IP or hostname of your server\n• Server Port - Port your server is running on\n• Client Key - Authentication key for secure uploads\n\nYour administrator should have provided these details."
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/welcomeDescription" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/nextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="Continue to Setup"
|
||||||
|
android:padding="12dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -5,4 +5,9 @@
|
|||||||
android:title="Upload History"
|
android:title="Upload History"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:showAsAction="never" />
|
android:showAsAction="never" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:title="Settings"
|
||||||
|
android:orderInCategory="200"
|
||||||
|
android:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
Reference in New Issue
Block a user