diff --git a/app/build.gradle b/app/build.gradle
index 43f6656..1bebb51 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,6 +56,9 @@ dependencies {
implementation 'com.android.support:support-annotations:28.0.0'
implementation 'androidx.palette:palette:1.0.0'
+ // QR Code scanning
+ implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
+
// Room
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index edb8cbc..212ac86 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
65535) {
+ throw new QrConfigException("Port must be between 1 and 65535");
+ }
+ if (key.trim().isEmpty()) {
+ throw new QrConfigException("Client key cannot be empty");
+ }
+
+ return new QrServerConfig(server.trim(), port, key.trim());
+
+ } catch (JSONException e) {
+ throw new QrConfigException("Invalid QR code format: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Custom exception for QR configuration errors
+ */
+ public static class QrConfigException extends Exception {
+ public QrConfigException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/app/src/main/java/com/mattintech/simplelogupload/util/PermissionUtil.java b/app/src/main/java/com/mattintech/simplelogupload/util/PermissionUtil.java
index bae034a..79dc628 100644
--- a/app/src/main/java/com/mattintech/simplelogupload/util/PermissionUtil.java
+++ b/app/src/main/java/com/mattintech/simplelogupload/util/PermissionUtil.java
@@ -24,7 +24,8 @@ public class PermissionUtil {
public static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 101;
public static final int REQUEST_NOTIFICATION_PERMISSION = 102;
-
+ public static final int REQUEST_CAMERA_PERMISSION = 103;
+
/**
* Checks if the app has notification permission
*
@@ -93,4 +94,31 @@ public class PermissionUtil {
}
}
}
+
+ /**
+ * Checks if the app has camera permission
+ *
+ * @param context The context
+ * @return true if the app has camera permission, false otherwise
+ */
+ public static boolean hasCameraPermission(Context context) {
+ return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Requests camera permission
+ *
+ * @param activity The activity
+ */
+ public static void requestCameraPermission(Activity activity) {
+ if (!hasCameraPermission(activity)) {
+ Log.d(TAG, "Requesting camera permission");
+ ActivityCompat.requestPermissions(
+ activity,
+ new String[]{Manifest.permission.CAMERA},
+ REQUEST_CAMERA_PERMISSION
+ );
+ }
+ }
}
diff --git a/app/src/main/java/com/mattintech/simplelogupload/util/QrScannerUtil.java b/app/src/main/java/com/mattintech/simplelogupload/util/QrScannerUtil.java
new file mode 100644
index 0000000..8045bce
--- /dev/null
+++ b/app/src/main/java/com/mattintech/simplelogupload/util/QrScannerUtil.java
@@ -0,0 +1,54 @@
+package com.mattintech.simplelogupload.util;
+
+import com.journeyapps.barcodescanner.ScanOptions;
+import com.mattintech.simplelogupload.model.QrServerConfig;
+
+/**
+ * Utility class for QR code scanning
+ */
+public class QrScannerUtil {
+
+ /**
+ * Callback interface for QR scan results
+ */
+ public interface QrScanCallback {
+ void onQrScanned(QrServerConfig config);
+ void onQrScanCancelled();
+ void onQrScanError(String errorMessage);
+ }
+
+ /**
+ * Create scan options for QR code scanning
+ *
+ * @return ScanOptions configured for QR codes
+ */
+ public static ScanOptions createScanOptions() {
+ ScanOptions options = new ScanOptions();
+ options.setDesiredBarcodeFormats(ScanOptions.QR_CODE);
+ options.setPrompt("Scan server configuration QR code");
+ options.setBeepEnabled(true);
+ options.setBarcodeImageEnabled(false);
+ options.setOrientationLocked(true);
+ return options;
+ }
+
+ /**
+ * Process scan result
+ *
+ * @param contents The scanned QR code contents
+ * @param callback The callback to handle the result
+ */
+ public static void processScanResult(String contents, QrScanCallback callback) {
+ if (contents == null) {
+ callback.onQrScanCancelled();
+ return;
+ }
+
+ try {
+ QrServerConfig config = QrServerConfig.fromQrData(contents);
+ callback.onQrScanned(config);
+ } catch (QrServerConfig.QrConfigException e) {
+ callback.onQrScanError(e.getMessage());
+ }
+ }
+}
diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java
index 77b6651..89844e8 100644
--- a/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java
+++ b/app/src/main/java/com/mattintech/simplelogupload/view/ServerSetupActivity.java
@@ -1,28 +1,38 @@
package com.mattintech.simplelogupload.view;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
+import com.journeyapps.barcodescanner.ScanContract;
+import com.journeyapps.barcodescanner.ScanOptions;
+import com.journeyapps.barcodescanner.ScanIntentResult;
import com.mattintech.simplelogupload.R;
import com.mattintech.simplelogupload.constant.AppConstants;
+import com.mattintech.simplelogupload.model.QrServerConfig;
+import com.mattintech.simplelogupload.util.PermissionUtil;
import com.mattintech.simplelogupload.util.PreferencesUtil;
+import com.mattintech.simplelogupload.util.QrScannerUtil;
/**
* Activity for server setup
*/
public class ServerSetupActivity extends AppCompatActivity {
private static final String TAG = AppConstants.APP_TAG + "ServerSetupActivity";
-
+
private TextInputEditText serverAddressInput;
private TextInputEditText serverPortInput;
private TextInputEditText clientKeyInput;
+ private ActivityResultLauncher qrScannerLauncher;
+ private Button scanQrButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -34,7 +44,17 @@ public class ServerSetupActivity extends AppCompatActivity {
serverPortInput = findViewById(R.id.serverPortInput);
clientKeyInput = findViewById(R.id.clientKeyInput);
Button finishSetupButton = findViewById(R.id.finishSetupButton);
-
+ scanQrButton = findViewById(R.id.scanQrButton);
+
+ // Initialize QR scanner launcher
+ qrScannerLauncher = registerForActivityResult(
+ new ScanContract(),
+ result -> handleQrScanResult(result)
+ );
+
+ // Set up QR scan button click listener
+ scanQrButton.setOnClickListener(v -> initiateQrScan());
+
// Set up action bar
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle("Server Setup");
@@ -103,7 +123,81 @@ public class ServerSetupActivity extends AppCompatActivity {
Toast.makeText(this, "Error saving settings: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
-
+
+ /**
+ * Initiate QR code scan
+ */
+ private void initiateQrScan() {
+ // Check camera permission
+ if (!PermissionUtil.hasCameraPermission(this)) {
+ PermissionUtil.requestCameraPermission(this);
+ return;
+ }
+
+ // Launch scanner
+ ScanOptions options = QrScannerUtil.createScanOptions();
+ qrScannerLauncher.launch(options);
+ }
+
+ /**
+ * Handle QR scan result
+ *
+ * @param result The scan result
+ */
+ private void handleQrScanResult(ScanIntentResult result) {
+ QrScannerUtil.processScanResult(
+ result.getContents(),
+ new QrScannerUtil.QrScanCallback() {
+ @Override
+ public void onQrScanned(QrServerConfig config) {
+ // Auto-fill the input fields
+ serverAddressInput.setText(config.getServer());
+ serverPortInput.setText(String.valueOf(config.getPort()));
+ clientKeyInput.setText(config.getKey());
+
+ Toast.makeText(ServerSetupActivity.this,
+ R.string.qr_scan_success,
+ Toast.LENGTH_SHORT).show();
+
+ Log.d(TAG, "QR code scanned successfully");
+ }
+
+ @Override
+ public void onQrScanCancelled() {
+ Toast.makeText(ServerSetupActivity.this,
+ R.string.qr_scan_cancelled,
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onQrScanError(String errorMessage) {
+ Toast.makeText(ServerSetupActivity.this,
+ getString(R.string.qr_scan_error, errorMessage),
+ Toast.LENGTH_LONG).show();
+ Log.e(TAG, "QR scan error: " + errorMessage);
+ }
+ }
+ );
+ }
+
+ /**
+ * Handle permission request results
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode == PermissionUtil.REQUEST_CAMERA_PERMISSION) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission granted, initiate scan
+ initiateQrScan();
+ } else {
+ // Permission denied
+ Toast.makeText(this, R.string.camera_permission_denied, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
diff --git a/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java b/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java
index 8110644..e9178de 100644
--- a/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java
+++ b/app/src/main/java/com/mattintech/simplelogupload/view/SettingsActivity.java
@@ -1,28 +1,38 @@
package com.mattintech.simplelogupload.view;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
+import com.journeyapps.barcodescanner.ScanContract;
+import com.journeyapps.barcodescanner.ScanOptions;
+import com.journeyapps.barcodescanner.ScanIntentResult;
import com.mattintech.simplelogupload.R;
import com.mattintech.simplelogupload.constant.AppConstants;
+import com.mattintech.simplelogupload.model.QrServerConfig;
+import com.mattintech.simplelogupload.util.PermissionUtil;
import com.mattintech.simplelogupload.util.PreferencesUtil;
+import com.mattintech.simplelogupload.util.QrScannerUtil;
/**
* 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;
-
+ private ActivityResultLauncher qrScannerLauncher;
+ private Button scanQrButton;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -34,7 +44,17 @@ public class SettingsActivity extends AppCompatActivity {
clientKeyInput = findViewById(R.id.clientKeyInput);
Button saveButton = findViewById(R.id.saveSettingsButton);
Button resetButton = findViewById(R.id.resetSettingsButton);
-
+ scanQrButton = findViewById(R.id.scanQrButton);
+
+ // Initialize QR scanner launcher
+ qrScannerLauncher = registerForActivityResult(
+ new ScanContract(),
+ result -> handleQrScanResult(result)
+ );
+
+ // Set up QR scan button click listener
+ scanQrButton.setOnClickListener(v -> initiateQrScan());
+
// Set up action bar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -127,7 +147,81 @@ public class SettingsActivity extends AppCompatActivity {
loadSettings();
Toast.makeText(this, "Settings reset to defaults", Toast.LENGTH_SHORT).show();
}
-
+
+ /**
+ * Initiate QR code scan
+ */
+ private void initiateQrScan() {
+ // Check camera permission
+ if (!PermissionUtil.hasCameraPermission(this)) {
+ PermissionUtil.requestCameraPermission(this);
+ return;
+ }
+
+ // Launch scanner
+ ScanOptions options = QrScannerUtil.createScanOptions();
+ qrScannerLauncher.launch(options);
+ }
+
+ /**
+ * Handle QR scan result
+ *
+ * @param result The scan result
+ */
+ private void handleQrScanResult(ScanIntentResult result) {
+ QrScannerUtil.processScanResult(
+ result.getContents(),
+ new QrScannerUtil.QrScanCallback() {
+ @Override
+ public void onQrScanned(QrServerConfig config) {
+ // Auto-fill the input fields
+ serverAddressInput.setText(config.getServer());
+ serverPortInput.setText(String.valueOf(config.getPort()));
+ clientKeyInput.setText(config.getKey());
+
+ Toast.makeText(SettingsActivity.this,
+ R.string.qr_scan_success,
+ Toast.LENGTH_SHORT).show();
+
+ Log.d(TAG, "QR code scanned successfully");
+ }
+
+ @Override
+ public void onQrScanCancelled() {
+ Toast.makeText(SettingsActivity.this,
+ R.string.qr_scan_cancelled,
+ Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onQrScanError(String errorMessage) {
+ Toast.makeText(SettingsActivity.this,
+ getString(R.string.qr_scan_error, errorMessage),
+ Toast.LENGTH_LONG).show();
+ Log.e(TAG, "QR scan error: " + errorMessage);
+ }
+ }
+ );
+ }
+
+ /**
+ * Handle permission request results
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode == PermissionUtil.REQUEST_CAMERA_PERMISSION) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission granted, initiate scan
+ initiateQrScan();
+ } else {
+ // Permission denied
+ Toast.makeText(this, R.string.camera_permission_denied, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
diff --git a/app/src/main/res/drawable/ic_qr_scan.xml b/app/src/main/res/drawable/ic_qr_scan.xml
new file mode 100644
index 0000000..0fd35ad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_qr_scan.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_server_setup.xml b/app/src/main/res/layout/activity_server_setup.xml
index 01ee407..413d12f 100644
--- a/app/src/main/res/layout/activity_server_setup.xml
+++ b/app/src/main/res/layout/activity_server_setup.xml
@@ -88,6 +88,27 @@
+
+
+
+
+
+
+
+