diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b799aea5..ab05fdce 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,10 +10,15 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - api-level: [19, 28] + api-level: [21, 29] steps: - name: Checkout uses: actions/checkout@v4 + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Set up environment uses: actions/setup-java@v4 with: @@ -26,7 +31,7 @@ jobs: id: avd-cache with: path: | - ~.android/avd/* + ~/.android/avd/* ~/.android/adb* key: avd-${{ matrix.api-level }} - name: Create AVD and generate snapshot for caching @@ -47,4 +52,4 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: ./gradlew connectedCheck \ No newline at end of file + script: adb uninstall com.enioka.scanner.sdk.mock.test; adb uninstall com.enioka.scanner.service; ./gradlew connectedCheck --stacktrace diff --git a/demoscannerapp/build.gradle b/demoscannerapp/build.gradle index 4aaea072..7a81d975 100644 --- a/demoscannerapp/build.gradle +++ b/demoscannerapp/build.gradle @@ -8,14 +8,21 @@ android { targetSdkVersion 28 versionCode 1 versionName "1.0" - + vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + debuggable false + applicationIdSuffix ".release" + } + debug { + debuggable true + applicationIdSuffix ".debug" } } lint { @@ -63,6 +70,7 @@ dependencies { //noinspection GradleCompatible implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.android.support.constraint:constraint-layout:2.0.4' + implementation 'com.google.android.material:material:1.12.0' // Useless test stuff testImplementation 'junit:junit:4.12' diff --git a/demoscannerapp/src/main/AndroidManifest.xml b/demoscannerapp/src/main/AndroidManifest.xml index d4602ac3..b519be2e 100644 --- a/demoscannerapp/src/main/AndroidManifest.xml +++ b/demoscannerapp/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + - - + - + - - \ No newline at end of file diff --git a/demoscannerapp/src/main/java/com/enioka/scanner/demo/ScannerTesterActivity.java b/demoscannerapp/src/main/java/com/enioka/scanner/demo/ScannerTesterActivity.java deleted file mode 100644 index b2de84fb..00000000 --- a/demoscannerapp/src/main/java/com/enioka/scanner/demo/ScannerTesterActivity.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.enioka.scanner.demo; - -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import android.util.Log; - -import com.enioka.scanner.activities.ScannerCompatActivity; -import com.enioka.scanner.data.Barcode; - -import java.io.BufferedWriter; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; -import java.util.Date; -import java.util.List; - -public class ScannerTesterActivity extends ScannerCompatActivity { - private static final String LOG_TAG = "ScannerTesterActivity"; - - private Uri logFileUri = null; - private int WRITE_REQUEST_CODE = 123; - private int WRITE_PERMISSION_REQUEST_CODE = 124; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - createLog(); - } else { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_PERMISSION_REQUEST_CODE); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == WRITE_PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - createLog(); - } - } - - private boolean isExternalStorageWritable() { - String state = Environment.getExternalStorageState(); - return Environment.MEDIA_MOUNTED.equals(state); - } - - private void createLog() { - if (!isExternalStorageWritable()) { - return; - } - - String fileName = (new Date()).getTime() + "_scanner_test_log.csv"; - - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("text/csv"); - intent.putExtra(Intent.EXTRA_TITLE, fileName); - startActivityForResult(intent, WRITE_REQUEST_CODE); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - logFileUri = data.getData(); - if (logFileUri != null) { - Log.i(LOG_TAG, "Log file will be written at: " + logFileUri.toString()); - } - } - } - - private synchronized void writeResultToLog(Barcode data) { - String dataLine = (new Date()).getTime() + "," + data.getBarcode() + "," + data.getBarcodeType().code; - - try (OutputStream os = getContentResolver().openOutputStream(logFileUri, "wa")) { - if (os == null) { - return; - } - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, Charset.forName("UTF8"))); - writer.write(dataLine, 0, dataLine.length()); - writer.newLine(); - writer.flush(); - Log.d(LOG_TAG, dataLine); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override - public void onData(List data) { - super.onData(data); - - if (logFileUri != null) { - for (Barcode br : data) { - writeResultToLog(br); - } - } - } -} diff --git a/demoscannerapp/src/main/java/com/enioka/scanner/demo/SettingsActivity.java b/demoscannerapp/src/main/java/com/enioka/scanner/demo/SettingsActivity.java index 4c8b45f3..a969dfa0 100644 --- a/demoscannerapp/src/main/java/com/enioka/scanner/demo/SettingsActivity.java +++ b/demoscannerapp/src/main/java/com/enioka/scanner/demo/SettingsActivity.java @@ -5,21 +5,26 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; +import androidx.core.content.ContextCompat; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; -import android.widget.Switch; -import android.widget.TextView; import com.enioka.scanner.api.ScannerSearchOptions; import com.enioka.scanner.data.BarcodeType; import com.enioka.scanner.service.ScannerService; import com.enioka.scanner.service.ScannerServiceApi; +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.checkbox.MaterialCheckBox; +import com.google.android.material.materialswitch.MaterialSwitch; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; public class SettingsActivity extends AppCompatActivity { @@ -28,24 +33,94 @@ public class SettingsActivity extends AppCompatActivity { */ protected String[] availableProvidersKey = null; + /** + * Segmented button preferences keys + */ + protected static final String PREFS_KEY = "segmentedButton"; + + /** + * Enable logging preferences key + */ + public static final String ENABLE_LOGGING_KEY = "enableLogging"; + /** + * Allow camera fallback preferences key + */ + public static final String ALLOW_CAMERA_FALLBACK_KEY = "allowCameraFallback"; + /** + * Enable keep aspect ratio preferences key + */ + public static final String ENABLE_KEEP_ASPECT_RATIO_KEY = "enableKeepAspectRatio"; + + /** + * List of provider views ids + */ + protected List providerViews = new ArrayList<>(); + + /** + * Save button + */ + protected MaterialButton buttonSave; + + /** + * topAppBar + */ + protected MaterialToolbar topAppBar; + + /** + * Select all segmented button + */ + protected MaterialButton bt_all; + + /** + * Select specific segmented button + */ + protected MaterialButton bt_spec; + + /** + * Select none segmented button + */ + protected MaterialButton bt_none; + + /** + * SegmentedButtons state + */ + protected int segmentedButtonState = 0; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); + // Setup save button listener + buttonSave = findViewById(R.id.button_save); + buttonSave.setOnClickListener(this::onClickSave); + + // Segmented toggle buttons + bt_all = findViewById(R.id.button_all); + bt_spec = findViewById(R.id.button_specific); + bt_none = findViewById(R.id.button_none); + + // Add listener to segmented toggle buttons + bindToggleButton(); + final SharedPreferences preferences = this.getSharedPreferences("ScannerSearchPreferences", MODE_PRIVATE); final ScannerSearchOptions options = ScannerSearchOptions.defaultOptions(); - ((Switch) findViewById(R.id.switchWaitDisconnected)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_WAIT_DISCONNECTED_BOOLEAN, options.waitDisconnected)); - ((Switch) findViewById(R.id.switchReturnOnlyFirst)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_RETURN_ONLY_FIRST_BOOLEAN, options.returnOnlyFirst)); - ((Switch) findViewById(R.id.switchBluetooth)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_BT_BOOLEAN, options.useBlueTooth)); - ((Switch) findViewById(R.id.switchLaterConnections)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_KEEP_SEARCHING_BOOLEAN, options.allowLaterConnections)); - ((Switch) findViewById(R.id.switchInitialSearch)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INITIAL_SEARCH_BOOLEAN, options.allowInitialSearch)); - ((Switch) findViewById(R.id.switchPairingFlow)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_PAIRING_FLOW_BOOLEAN, options.allowPairingFlow)); - ((Switch) findViewById(R.id.switchIntentDevices)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INTENT_BOOLEAN, options.allowIntentDevices)); + ((MaterialSwitch) findViewById(R.id.switchWaitDisconnected)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_WAIT_DISCONNECTED_BOOLEAN, options.waitDisconnected)); + ((MaterialSwitch) findViewById(R.id.switchReturnOnlyFirst)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_RETURN_ONLY_FIRST_BOOLEAN, options.returnOnlyFirst)); + ((MaterialSwitch) findViewById(R.id.switchBluetooth)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_BT_BOOLEAN, options.useBlueTooth)); + ((MaterialSwitch) findViewById(R.id.switchLaterConnections)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_KEEP_SEARCHING_BOOLEAN, options.allowLaterConnections)); + ((MaterialSwitch) findViewById(R.id.switchInitialSearch)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INITIAL_SEARCH_BOOLEAN, options.allowInitialSearch)); + ((MaterialSwitch) findViewById(R.id.switchPairingFlow)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_PAIRING_FLOW_BOOLEAN, options.allowPairingFlow)); + ((MaterialSwitch) findViewById(R.id.switchIntentDevices)).setChecked(preferences.getBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INTENT_BOOLEAN, options.allowIntentDevices)); + ((MaterialSwitch) findViewById(R.id.switchEnableLogging)).setChecked(preferences.getBoolean(ENABLE_LOGGING_KEY, false)); + ((MaterialSwitch) findViewById(R.id.switchAllowCameraFallback)).setChecked(preferences.getBoolean(ALLOW_CAMERA_FALLBACK_KEY, false)); + ((MaterialSwitch) findViewById(R.id.switchEnableKeepAspectRatio)).setChecked(preferences.getBoolean(ENABLE_KEEP_ASPECT_RATIO_KEY, false)); final Set allowedProviderKeys = preferences.getStringSet(ScannerServiceApi.EXTRA_SEARCH_ALLOWED_PROVIDERS_STRING_ARRAY, Collections.emptySet()); - final Set excludedProviderKeys = preferences.getStringSet(ScannerServiceApi.EXTRA_SEARCH_EXCLUDED_PROVIDERS_STRING_ARRAY, Collections.emptySet()); + + // Set the state of the segmented button + segmentedButtonState = preferences.getInt(PREFS_KEY, 0); final Set symbologySelection = new HashSet<>(); for(String symbology: preferences.getStringSet(ScannerServiceApi.EXTRA_SYMBOLOGY_SELECTION, ScannerService.defaultSymbologyByName())) { @@ -69,99 +144,58 @@ protected void onCreate(Bundle savedInstanceState) { } // Create the UI - int topViewIdAllowed = R.id.textViewAllowedProviders; - int topViewIdExcluded = R.id.textViewExcludedProviders; + int topViewId = R.id.toggleButtonProvider; for (String providerKey : availableProvidersKey) { // Create the UI for the allowed providers - TextView textViewAllowed = generateTextView(providerKey, topViewIdAllowed); - textViewAllowed.setTag("allowed_text_" + providerKey); - CheckBox checkBoxAllowed = generateCheckBox(providerKey, topViewIdAllowed, textViewAllowed.getId()); - checkBoxAllowed.setTag("allowed_check_" + providerKey); + CheckBox checkBox = generateCheckBox(providerKey, topViewId); + checkBox.setTag("checkbox_" + providerKey); if (allowedProviderKeys.contains(providerKey)) { - checkBoxAllowed.setChecked(true); + checkBox.setChecked(true); } - // Create the UI for the excluded providers - TextView textViewExcluded = generateTextView(providerKey, topViewIdExcluded); - textViewExcluded.setTag("excluded_text_" + providerKey); - CheckBox checkBoxExcluded = generateCheckBox(providerKey, topViewIdExcluded, textViewExcluded.getId()); - checkBoxExcluded.setTag("excluded_check_" + providerKey); - - if (excludedProviderKeys.contains(providerKey)) { - checkBoxExcluded.setChecked(true); - } + topViewId = checkBox.getId(); + } - topViewIdAllowed = textViewAllowed.getId(); - topViewIdExcluded = textViewExcluded.getId(); + if (availableProvidersKey.length == 0) { + // Hide the provider toggle button + findViewById(R.id.toggleButtonProvider).setVisibility(View.GONE); + findViewById(R.id.textEmptyProviderList).setVisibility(View.VISIBLE); + } else { + // Update the layout + updateConstraintLayout(topViewId); } - // Update the layout - updateConstraintLayout(topViewIdAllowed, topViewIdExcluded); + // Set the state of the segmented button (default is all) + setSegmentedButtonState(); } - private void updateConstraintLayout(int topViewIdAllowed, int topViewIdExcluded) { + private void updateConstraintLayout(int topViewId) { ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone((ConstraintLayout) findViewById(R.id.constraintLayoutSettings)); // Init marginTop - int marginTop = getResources().getDimensionPixelSize(R.dimen.layout_margin_top_header_text); + int marginTop = getResources().getDimensionPixelSize(R.dimen.layout_margin_top_divider); // Set top bottom constraints - constraintSet.connect(R.id.textViewExcludedProviders, ConstraintSet.TOP, topViewIdAllowed, ConstraintSet.BOTTOM, marginTop); - constraintSet.connect(R.id.textSymbologySelection, ConstraintSet.TOP, topViewIdExcluded, ConstraintSet.BOTTOM, marginTop); + constraintSet.connect(R.id.dividerSettingsSymbology, ConstraintSet.TOP, topViewId, ConstraintSet.BOTTOM, marginTop); // Apply constraints constraintSet.applyTo(findViewById(R.id.constraintLayoutSettings)); } - private TextView generateTextView(String providerKey, int topViewId) { - TextView textView = new TextView(this); - textView.setId(View.generateViewId()); - - // Add TextView to the parent layout - ViewGroup parentLayout = findViewById(R.id.constraintLayoutSettings); - parentLayout.addView(textView); - - // Set layout parameters - ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.layout_height)); - - textView.setLayoutParams(layoutParams); - - // Get the right text if the provider is known - int textResources = getResources().getIdentifier(providerKey, "string", this.getPackageName()); - - if (textResources != 0) { - textView.setText(textResources); - } else { - textView.setText(providerKey); - } - - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone((ConstraintLayout) parentLayout); - // Set margins - int marginStart = getResources().getDimensionPixelSize(R.dimen.layout_margin_start_text); - int marginEnd = getResources().getDimensionPixelSize(R.dimen.layout_margin_end); - int marginTop = getResources().getDimensionPixelSize(R.dimen.layout_margin_top); + private CheckBox generateCheckBox(String providerKey, int topViewId) { + MaterialCheckBox checkBox = new MaterialCheckBox(this); - // Set constraints for the TextView - constraintSet.connect(textView.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, marginStart); - constraintSet.connect(textView.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, marginEnd); - constraintSet.connect(textView.getId(), ConstraintSet.TOP, topViewId, ConstraintSet.BOTTOM, marginTop); - constraintSet.applyTo((ConstraintLayout) parentLayout); - - return textView; - } + // Gen checkBox id + int checkBoxId = View.generateViewId(); + checkBox.setId(checkBoxId); + providerViews.add(checkBoxId); - private CheckBox generateCheckBox(String providerKey, int topViewId, int rightViewId) { - CheckBox checkBox = new CheckBox(this); - checkBox.setId(View.generateViewId()); ViewGroup parentLayout = findViewById(R.id.constraintLayoutSettings); // Add CheckBox to the parent layout @@ -186,24 +220,37 @@ private CheckBox generateCheckBox(String providerKey, int topViewId, int rightVi constraintSet.connect(checkBox.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, marginStart); constraintSet.connect(checkBox.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, marginEnd); constraintSet.connect(checkBox.getId(), ConstraintSet.TOP, topViewId, ConstraintSet.BOTTOM, marginTop); - constraintSet.connect(checkBox.getId(), ConstraintSet.RIGHT, rightViewId, ConstraintSet.LEFT, 0); constraintSet.applyTo((ConstraintLayout) parentLayout); checkBox.setChecked(false); + // Get the right text if the provider is known + int textResources = getResources().getIdentifier(providerKey, "string", this.getPackageName()); + + if (textResources != 0) { + // Set the custom text if available + checkBox.setText(textResources); + } else { + // Otherwise set to the provider key + checkBox.setText(providerKey); + } + return checkBox; } public void onClickSave(View v) { final SharedPreferences.Editor editor = this.getSharedPreferences("ScannerSearchPreferences", MODE_PRIVATE).edit(); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_WAIT_DISCONNECTED_BOOLEAN, ((Switch) findViewById(R.id.switchWaitDisconnected)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_RETURN_ONLY_FIRST_BOOLEAN, ((Switch) findViewById(R.id.switchReturnOnlyFirst)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_BT_BOOLEAN, ((Switch) findViewById(R.id.switchBluetooth)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_KEEP_SEARCHING_BOOLEAN, ((Switch) findViewById(R.id.switchLaterConnections)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INITIAL_SEARCH_BOOLEAN, ((Switch) findViewById(R.id.switchInitialSearch)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_PAIRING_FLOW_BOOLEAN, ((Switch) findViewById(R.id.switchPairingFlow)).isChecked()); - editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INTENT_BOOLEAN, ((Switch) findViewById(R.id.switchIntentDevices)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_WAIT_DISCONNECTED_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchWaitDisconnected)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_RETURN_ONLY_FIRST_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchReturnOnlyFirst)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_BT_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchBluetooth)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_KEEP_SEARCHING_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchLaterConnections)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INITIAL_SEARCH_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchInitialSearch)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_PAIRING_FLOW_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchPairingFlow)).isChecked()); + editor.putBoolean(ScannerServiceApi.EXTRA_SEARCH_ALLOW_INTENT_BOOLEAN, ((MaterialSwitch) findViewById(R.id.switchIntentDevices)).isChecked()); + editor.putBoolean(ENABLE_LOGGING_KEY, ((MaterialSwitch) findViewById(R.id.switchEnableLogging)).isChecked()); + editor.putBoolean(ALLOW_CAMERA_FALLBACK_KEY, ((MaterialSwitch) findViewById(R.id.switchAllowCameraFallback)).isChecked()); + editor.putBoolean(ENABLE_KEEP_ASPECT_RATIO_KEY, ((MaterialSwitch) findViewById(R.id.switchEnableKeepAspectRatio)).isChecked()); final Set allowedProviderKeys = new HashSet<>(); final Set excludedProviderKeys = new HashSet<>(); @@ -214,14 +261,16 @@ public void onClickSave(View v) { // Save the CheckBoxes state for allowed and excluded providers for (String providerKey : availableProvidersKey) { // Retrieve the CheckBoxes with tags - CheckBox checkBoxAllowed = parentLayout.findViewWithTag("allowed_check_" + providerKey); - CheckBox checkBoxExcluded = parentLayout.findViewWithTag("excluded_check_" + providerKey); + CheckBox checkBoxAllowed = parentLayout.findViewWithTag("checkbox_" + providerKey); - if (checkBoxAllowed.isChecked()) { - allowedProviderKeys.add(providerKey); + if (checkBoxAllowed == null) { + Log.w("SettingsActivity", "CheckBox not found for providerKey: " + providerKey); + continue; } - if (checkBoxExcluded.isChecked()) { + if (checkBoxAllowed.isChecked()) { + allowedProviderKeys.add(providerKey); + } else { excludedProviderKeys.add(providerKey); } } @@ -229,6 +278,8 @@ public void onClickSave(View v) { editor.putStringSet(ScannerServiceApi.EXTRA_SEARCH_ALLOWED_PROVIDERS_STRING_ARRAY, allowedProviderKeys); editor.putStringSet(ScannerServiceApi.EXTRA_SEARCH_EXCLUDED_PROVIDERS_STRING_ARRAY, excludedProviderKeys); + editor.putInt(PREFS_KEY, segmentedButtonState); + final Set symbologySelection = new HashSet<>(); if (((CheckBox) findViewById(R.id.checkSelectCode128)).isChecked()) { symbologySelection.add(BarcodeType.CODE128.name()); } if (((CheckBox) findViewById(R.id.checkSelectCode39)).isChecked()) { symbologySelection.add(BarcodeType.CODE39.name()); } @@ -242,4 +293,78 @@ public void onClickSave(View v) { editor.apply(); finish(); } + + + /** + * Bind toggle buttons + */ + private void bindToggleButton() { + bt_all.setOnClickListener(v -> { + if (bt_all.isChecked()) { + bt_all.setIcon(ContextCompat.getDrawable(this, R.drawable.check_all)); + bt_spec.setIcon(null); + bt_none.setIcon(null); + + for (int providerViewId : providerViews) { + // Force check and disable + ((CheckBox) findViewById(providerViewId)).setChecked(true); + findViewById(providerViewId).setEnabled(false); + } + + segmentedButtonState = 0; + } + }); + bt_spec.setOnClickListener(v -> { + if (bt_spec.isChecked()) { + bt_spec.setIcon(ContextCompat.getDrawable(this, R.drawable.search)); + bt_all.setIcon(null); + bt_none.setIcon(null); + + for (int providerViewId : providerViews) { + // Force enable + findViewById(providerViewId).setEnabled(true); + } + + segmentedButtonState = 1; + } + }); + bt_none.setOnClickListener(v -> { + if (bt_none.isChecked()) { + bt_none.setIcon(ContextCompat.getDrawable(this, R.drawable.cross)); + bt_spec.setIcon(null); + bt_all.setIcon(null); + + for (int providerViewId : providerViews) { + // Force uncheck and disable + ((CheckBox) findViewById(providerViewId)).setChecked(false); + findViewById(providerViewId).setEnabled(false); + } + + segmentedButtonState = 2; + } + }); + } + + /** + * Set the state of segmented button + */ + private void setSegmentedButtonState() { + switch (segmentedButtonState) { + case 0: + bt_all.performClick(); + bt_spec.setChecked(false); + bt_none.setChecked(false); + break; + case 1: + bt_all.setChecked(false); + bt_spec.performClick(); + bt_none.setChecked(false); + break; + case 2: + bt_all.setChecked(false); + bt_spec.setChecked(false); + bt_none.performClick(); + break; + } + } } diff --git a/demoscannerapp/src/main/java/com/enioka/scanner/demo/WelcomeActivity.java b/demoscannerapp/src/main/java/com/enioka/scanner/demo/WelcomeActivity.java index b80675ab..8f0f63ea 100644 --- a/demoscannerapp/src/main/java/com/enioka/scanner/demo/WelcomeActivity.java +++ b/demoscannerapp/src/main/java/com/enioka/scanner/demo/WelcomeActivity.java @@ -2,27 +2,72 @@ import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.net.Uri; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.constraintlayout.widget.ConstraintLayout; + +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.widget.LinearLayout; import com.enioka.scanner.LaserScanner; import com.enioka.scanner.api.ScannerSearchOptions; import com.enioka.scanner.service.ScannerService; import com.enioka.scanner.service.ScannerServiceApi; +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.ArrayList; import java.util.List; public class WelcomeActivity extends AppCompatActivity { private List availableProviders = new ArrayList<>(); + protected MaterialButton bt1 = null; + protected FloatingActionButton bugReportBt = null; + protected MaterialButton bt5 = null; + + protected MaterialToolbar toolbar = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + boolean landscape = this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + setContentView(R.layout.activity_welcome); + // Dynamically switch the layout based on the orientation + if (landscape) { + switchWelcomeActivity(false); + } + LaserScanner.discoverProviders(this, () -> {}); availableProviders = LaserScanner.getProviderCache(); + initViews(); + } + + // Show the menu in the top action tool bar + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + private void initViews() { + bt1 = findViewById(R.id.bt_scanner); + bt5 = findViewById(R.id.bt_settings); + toolbar = findViewById(R.id.topAppBar); + bugReportBt = findViewById(R.id.bug_report_button); + + bt1.setOnClickListener(this::onClickBt1); + bt5.setOnClickListener(this::onClickBt5); + bugReportBt.setOnClickListener(this::onClickReportBug); + setSupportActionBar(toolbar); } // Scanner activity @@ -45,20 +90,59 @@ public void onClickBt1(View v) { // add symbology final String[] symbologies = preferences.getStringSet(ScannerServiceApi.EXTRA_SYMBOLOGY_SELECTION, ScannerService.defaultSymbologyByName()).toArray(new String[0]); intent.putExtra(ScannerServiceApi.EXTRA_SYMBOLOGY_SELECTION, symbologies); + // add logging intent extra + intent.putExtra(SettingsActivity.ENABLE_LOGGING_KEY, preferences.getBoolean(SettingsActivity.ENABLE_LOGGING_KEY, false)); + // add allow camera fallback intent extra + intent.putExtra(SettingsActivity.ALLOW_CAMERA_FALLBACK_KEY, preferences.getBoolean(SettingsActivity.ALLOW_CAMERA_FALLBACK_KEY, false)); + // add enable keep aspect ratio intent extra + intent.putExtra(SettingsActivity.ENABLE_KEEP_ASPECT_RATIO_KEY, preferences.getBoolean(SettingsActivity.ENABLE_KEEP_ASPECT_RATIO_KEY, false) ? 1 : 0); startActivity(intent); } - // Scanner Test activity - public void onClickBt4(View v) { - Intent intent = new Intent(this, ScannerTesterActivity.class); - startActivity(intent); - } - // Scanner settings activity public void onClickBt5(View v) { Intent intent = new Intent(this, SettingsActivity.class); intent.putStringArrayListExtra("providers", (ArrayList) availableProviders); startActivity(intent); } + + // Report bug function button + public void onClickReportBug(View v) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/enioka-Haute-Couture/enioka_scan/issues/new")); + startActivity(browserIntent); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.github_button) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/enioka-Haute-Couture/enioka_scan")); + startActivity(browserIntent); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + switchWelcomeActivity(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT); + } + + public void switchWelcomeActivity(boolean portrait) { + View welcome_card = findViewById(R.id.welcome_card); + View barcode_image = findViewById(R.id.barcode); + ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) welcome_card.getLayoutParams(); + LinearLayout.LayoutParams paramsBarcode = (LinearLayout.LayoutParams) barcode_image.getLayoutParams(); + + if (portrait) { + params.matchConstraintPercentWidth = 0.8f; + paramsBarcode.height = 90 * (int) getResources().getDisplayMetrics().density; + } else { + params.matchConstraintPercentWidth = 0.5f; + paramsBarcode.height = 100 * (int) getResources().getDisplayMetrics().density; + } + welcome_card.setLayoutParams(params); + barcode_image.setLayoutParams(paramsBarcode); + } } diff --git a/demoscannerapp/src/main/res/drawable/arrow_left.xml b/demoscannerapp/src/main/res/drawable/arrow_left.xml new file mode 100644 index 00000000..8629664c --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/arrow_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/baseline_settings_24.xml b/demoscannerapp/src/main/res/drawable/baseline_settings_24.xml new file mode 100644 index 00000000..6593f3a4 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/baseline_settings_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/demoscannerapp/src/main/res/drawable/check_all.xml b/demoscannerapp/src/main/res/drawable/check_all.xml new file mode 100644 index 00000000..c9f30e74 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/check_all.xml @@ -0,0 +1,12 @@ + + + + diff --git a/demoscannerapp/src/main/res/drawable/cross.xml b/demoscannerapp/src/main/res/drawable/cross.xml new file mode 100644 index 00000000..3e3047fa --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/cross.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/gear_fill.xml b/demoscannerapp/src/main/res/drawable/gear_fill.xml new file mode 100644 index 00000000..ceb5a79e --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/gear_fill.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/github.xml b/demoscannerapp/src/main/res/drawable/github.xml new file mode 100644 index 00000000..7f70a1f9 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/github.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/rounded_barcode_scanner_24.xml b/demoscannerapp/src/main/res/drawable/rounded_barcode_scanner_24.xml new file mode 100644 index 00000000..be8fdb02 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/rounded_barcode_scanner_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/demoscannerapp/src/main/res/drawable/search.xml b/demoscannerapp/src/main/res/drawable/search.xml new file mode 100644 index 00000000..ca831a97 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/search.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/upc.xml b/demoscannerapp/src/main/res/drawable/upc.xml new file mode 100644 index 00000000..627d8ed2 --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/upc.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/drawable/upc_scan.xml b/demoscannerapp/src/main/res/drawable/upc_scan.xml new file mode 100644 index 00000000..530c2e9f --- /dev/null +++ b/demoscannerapp/src/main/res/drawable/upc_scan.xml @@ -0,0 +1,9 @@ + + + diff --git a/demoscannerapp/src/main/res/layout/activity_settings.xml b/demoscannerapp/src/main/res/layout/activity_settings.xml index bd69fb07..e0678f3c 100644 --- a/demoscannerapp/src/main/res/layout/activity_settings.xml +++ b/demoscannerapp/src/main/res/layout/activity_settings.xml @@ -6,244 +6,459 @@ android:layout_height="match_parent" tools:context="com.enioka.scanner.demo.SettingsActivity"> + + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@+id/topAppBarSettings"> - - - - - - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent"/> - + + + + + + + + + - - - - + + + + + + + + - - - - + + + + + + + + - - - - + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/linearLayoutEnableKeepAspectRatio"/> - + + app:layout_constraintTop_toBottomOf="@+id/dividerSettingsAllowedProvider" /> - - + app:layout_constraintTop_toBottomOf="@+id/textViewAllowedProviders"> - + - + + + + + + app:layout_constraintTop_toBottomOf="@+id/toggleButtonProvider" /> - - - + app:layout_constraintTop_toBottomOf="@+id/textEmptyProviderList"/> + app:layout_constraintTop_toBottomOf="@+id/dividerSettingsSymbology" /> + - + + app:layout_constraintTop_toBottomOf="@+id/textSymbologySelectionCaption" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectCode128" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectCode39" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectDis25" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectInt25" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectEan13" /> - - - + app:layout_constraintTop_toBottomOf="@+id/checkSelectQrCode" /> - - -