Add QR code scanning for server configuration
Adds QR code scanning functionality to both ServerSetupActivity and SettingsActivity, allowing users to auto-fill server configuration by scanning a QR code from the server's admin dashboard. Changes: - Add ZXing Android Embedded library (v4.3.0) for QR scanning - Add CAMERA permission to AndroidManifest - Create QrServerConfig model to parse and validate QR JSON data - Create QrScannerUtil for shared scanning functionality - Extend PermissionUtil with camera permission handling - Add "Scan QR Code" button to ServerSetupActivity with hint text - Add "Scan QR Code" button to SettingsActivity - Add QR scan icon and string resources - Configure Gradle Java toolchain auto-detection The QR code contains JSON with server address, port, and client key which are automatically populated into the input fields when scanned.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.mattintech.simplelogupload.model;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Model class for QR code server configuration
|
||||
*/
|
||||
public class QrServerConfig {
|
||||
private final String server;
|
||||
private final int port;
|
||||
private final String key;
|
||||
|
||||
private QrServerConfig(String server, int port, String key) {
|
||||
this.server = server;
|
||||
this.port = port;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse QR code JSON data
|
||||
*
|
||||
* @param qrData The QR code string data
|
||||
* @return QrServerConfig object
|
||||
* @throws QrConfigException if parsing fails or data is invalid
|
||||
*/
|
||||
public static QrServerConfig fromQrData(String qrData) throws QrConfigException {
|
||||
if (qrData == null || qrData.trim().isEmpty()) {
|
||||
throw new QrConfigException("QR code data is empty");
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject(qrData);
|
||||
|
||||
// Extract fields
|
||||
if (!json.has("server")) {
|
||||
throw new QrConfigException("Missing 'server' field in QR code");
|
||||
}
|
||||
if (!json.has("port")) {
|
||||
throw new QrConfigException("Missing 'port' field in QR code");
|
||||
}
|
||||
if (!json.has("key")) {
|
||||
throw new QrConfigException("Missing 'key' field in QR code");
|
||||
}
|
||||
|
||||
String server = json.getString("server");
|
||||
int port = json.getInt("port");
|
||||
String key = json.getString("key");
|
||||
|
||||
// Validate
|
||||
if (server.trim().isEmpty()) {
|
||||
throw new QrConfigException("Server address cannot be empty");
|
||||
}
|
||||
if (port <= 0 || port > 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
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
|
||||
@@ -23,6 +31,8 @@ public class ServerSetupActivity extends AppCompatActivity {
|
||||
private TextInputEditText serverAddressInput;
|
||||
private TextInputEditText serverPortInput;
|
||||
private TextInputEditText clientKeyInput;
|
||||
private ActivityResultLauncher<ScanOptions> qrScannerLauncher;
|
||||
private Button scanQrButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -34,6 +44,16 @@ 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) {
|
||||
@@ -104,6 +124,80 @@ public class ServerSetupActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
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
|
||||
@@ -22,6 +30,8 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
private TextInputEditText serverAddressInput;
|
||||
private TextInputEditText serverPortInput;
|
||||
private TextInputEditText clientKeyInput;
|
||||
private ActivityResultLauncher<ScanOptions> qrScannerLauncher;
|
||||
private Button scanQrButton;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -34,6 +44,16 @@ 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) {
|
||||
@@ -128,6 +148,80 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
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();
|
||||
|
||||
39
app/src/main/res/drawable/ic_qr_scan.xml
Normal file
39
app/src/main/res/drawable/ic_qr_scan.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,11h8V3H3V11zM5,5h4v4H5V5z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,19h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M13,13h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,15h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M13,17h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,19h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,17h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,13h2v2h-2z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,15h2v2h-2z"/>
|
||||
</vector>
|
||||
@@ -88,6 +88,27 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- QR Code Scan Button -->
|
||||
<Button
|
||||
android:id="@+id/scanQrButton"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/scan_qr_code_button"
|
||||
android:drawableLeft="@drawable/ic_qr_scan"
|
||||
android:drawablePadding="8dp"
|
||||
android:paddingVertical="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/scan_qr_code_hint"
|
||||
android:textAlignment="center"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/testConnectionInfo"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -67,6 +67,18 @@
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- QR Code Scan Button -->
|
||||
<Button
|
||||
android:id="@+id/scanQrButton"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/scan_qr_code_button"
|
||||
android:drawableLeft="@drawable/ic_qr_scan"
|
||||
android:drawablePadding="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/saveSettingsButton"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -67,4 +67,13 @@
|
||||
<string name="expiry_time_note">Maximum 24 hours (1 day)</string>
|
||||
<string name="cancel_button">Cancel</string>
|
||||
<string name="upload_button">Upload</string>
|
||||
|
||||
<!-- QR Code Scanning -->
|
||||
<string name="scan_qr_code_button">Scan QR Code</string>
|
||||
<string name="scan_qr_code_hint">Scan a QR code from your server admin panel to auto-fill settings</string>
|
||||
<string name="qr_scan_success">Configuration loaded from QR code</string>
|
||||
<string name="qr_scan_cancelled">QR code scan cancelled</string>
|
||||
<string name="qr_scan_error">QR code error: %1$s</string>
|
||||
<string name="camera_permission_required">Camera permission is required to scan QR codes</string>
|
||||
<string name="camera_permission_denied">Camera permission denied. Please enable it in Settings to scan QR codes.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
android.suppressUnsupportedCompileSdk=36
|
||||
android.useAndroidX=true
|
||||
|
||||
# Enable Gradle toolchain auto-detection and auto-download
|
||||
org.gradle.java.installations.auto-detect=true
|
||||
org.gradle.java.installations.auto-download=true
|
||||
org.gradle.java.installations.paths=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
|
||||
|
||||
# JVM arguments for kapt with JDK 9+
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||
|
||||
Reference in New Issue
Block a user