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 @@ + +