diff --git a/README.md b/README.md index 837d0d2..5bd4de6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Keepass Fidelity

- Logo + Logo

A minimalist fidelity/loyalty card plugin

@@ -18,9 +18,9 @@
- - - + + +
LauncherViewEditLauncherViewEdit
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 82bd4a5..3c6e43d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,10 @@ android:versionName="1.1c"> + + + = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + resultPermission.launch(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + } else { + // resultLauncherOpenMediaBase.launch("image/*") + resultLauncherOpenMediaPick.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + } + return View(context) + } + + private fun startCreateEntry() { + val createEntryFragment = CreateEntry() + createEntryFragment.arguments = + KeepassWrapper.bundleCreate(null, this.code, this.fmt) + requireActivity().supportFragmentManager.beginTransaction() + .replace(R.id.container, createEntryFragment) + .commit() + } + + private fun scannerResult(code: String?, format: String?) { + if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) { + this.code = code + this.fmt = format + } + val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty() + requireActivity().runOnUiThread { + if (isDone) { + startCreateEntry() + } else { + parentFragmentManager.popBackStack() + ErrorToaster.nothingFound(context) + } + } + } + + private fun loadUri(it: Uri?) { + try { + run { + require(it != null) + + val file = requireContext().contentResolver.openInputStream(it) + val image = BitmapFactory.decodeStream(file) + BarcodeScanner.bitmapUseCase(image) { code, format -> + scannerResult(code, format) + } + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + println(e.message) + println(it) + ErrorToaster.noPermission(context) + parentFragmentManager.popBackStack() + } catch (e: IllegalArgumentException) { + ErrorToaster.nothingFound(context) + parentFragmentManager.popBackStack() + } catch (e: SecurityException) { + ErrorToaster.noPermission(context) + parentFragmentManager.popBackStack() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/helcel/fidelity/activity/fragment/Launcher.kt b/app/src/main/java/net/helcel/fidelity/activity/fragment/Launcher.kt index 21fd1a1..54ce24e 100644 --- a/app/src/main/java/net/helcel/fidelity/activity/fragment/Launcher.kt +++ b/app/src/main/java/net/helcel/fidelity/activity/fragment/Launcher.kt @@ -49,6 +49,11 @@ class Launcher : Fragment() { startScanner() hideMenuAdd() } + binding.btnOpen.setOnClickListener { + startFileScanner() + hideMenuAdd() + } + binding.btnManual.setOnClickListener { startCreateEntry() @@ -96,6 +101,10 @@ class Launcher : Fragment() { startFragment(Scanner()) } + private fun startFileScanner() { + startFragment(FileScanner()) + } + private fun startCreateEntry() { startFragment(CreateEntry()) } diff --git a/app/src/main/java/net/helcel/fidelity/activity/fragment/Scanner.kt b/app/src/main/java/net/helcel/fidelity/activity/fragment/Scanner.kt index 3ab5af4..589ec57 100644 --- a/app/src/main/java/net/helcel/fidelity/activity/fragment/Scanner.kt +++ b/app/src/main/java/net/helcel/fidelity/activity/fragment/Scanner.kt @@ -2,25 +2,23 @@ package net.helcel.fidelity.activity.fragment import android.Manifest import android.content.ContentValues -import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.CameraSelector import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import net.helcel.fidelity.R import net.helcel.fidelity.databinding.FragScannerBinding -import net.helcel.fidelity.tools.BarcodeScanner.getAnalysisUseCase +import net.helcel.fidelity.tools.BarcodeScanner.analysisUseCase +import net.helcel.fidelity.tools.ErrorToaster import net.helcel.fidelity.tools.KeepassWrapper -private const val CAMERA_PERMISSION_REQUEST_CODE = 1 - class Scanner : Fragment() { private lateinit var binding: FragScannerBinding @@ -28,6 +26,17 @@ class Scanner : Fragment() { private var code: String = "" private var fmt: String = "" + + private val resultPermissionRequest = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { + if (it) { + bindCameraUseCases() + } else { + parentFragmentManager.popBackStack() + ErrorToaster.noPermission(context) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -37,11 +46,8 @@ class Scanner : Fragment() { binding.btnScanDone.setOnClickListener { startCreateEntry() } - when (hasCameraPermission()) { - true -> bindCameraUseCases() - else -> requestPermission() - } binding.btnScanDone.isEnabled = false + resultPermissionRequest.launch(Manifest.permission.CAMERA) return binding.root } @@ -55,26 +61,16 @@ class Scanner : Fragment() { .commit() } - private fun hasCameraPermission() = - ActivityCompat.checkSelfPermission( - requireContext(), - Manifest.permission.CAMERA - ) == PackageManager.PERMISSION_GRANTED - - private fun requestPermission() { - ActivityCompat.requestPermissions( - requireActivity(), - arrayOf(Manifest.permission.CAMERA), - CAMERA_PERMISSION_REQUEST_CODE - ) - ActivityCompat.OnRequestPermissionsResultCallback { c, p, i -> - require(c == CAMERA_PERMISSION_REQUEST_CODE) - require(p.contains(Manifest.permission.CAMERA)) - val el = i[p.indexOf(Manifest.permission.CAMERA)] - if (el != PackageManager.PERMISSION_GRANTED) { - startCreateEntry() - } + private fun scannerResult(code: String?, format: String?) { + if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) { + this.code = code + this.fmt = format + } + val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty() + requireActivity().runOnUiThread { + binding.btnScanDone.isEnabled = isDone + binding.ScanActive.isEnabled = !isDone } } @@ -89,16 +85,8 @@ class Scanner : Fragment() { it.setSurfaceProvider(binding.cameraView.surfaceProvider) } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - val analysisUseCase = getAnalysisUseCase { code, format -> - if (!code.isNullOrEmpty() && !format.isNullOrEmpty()) { - this.code = code - this.fmt = format - } - val isDone = this.code.isNotEmpty() && this.fmt.isNotEmpty() - requireActivity().runOnUiThread { - binding.btnScanDone.isEnabled = isDone - binding.ScanActive.isEnabled = !isDone - } + val analysisUseCase = analysisUseCase { code, format -> + scannerResult(code, format) } try { cameraProvider.bindToLifecycle( diff --git a/app/src/main/java/net/helcel/fidelity/tools/BarcodeScanner.kt b/app/src/main/java/net/helcel/fidelity/tools/BarcodeScanner.kt index f356320..a981899 100644 --- a/app/src/main/java/net/helcel/fidelity/tools/BarcodeScanner.kt +++ b/app/src/main/java/net/helcel/fidelity/tools/BarcodeScanner.kt @@ -4,7 +4,6 @@ import android.graphics.Bitmap import androidx.annotation.OptIn import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageProxy import com.google.zxing.BinaryBitmap import com.google.zxing.MultiFormatReader import com.google.zxing.NotFoundException @@ -15,14 +14,13 @@ import net.helcel.fidelity.tools.BarcodeFormatConverter.formatToString import java.util.concurrent.Executors +@OptIn(ExperimentalGetImage::class) object BarcodeScanner { - @OptIn(ExperimentalGetImage::class) - private fun processImageProxy( - imageProxy: ImageProxy, + private fun processImage( + bitmap: Bitmap, cb: (String?, String?) -> Unit ) { - val bitmap = imageProxy.toBitmap() // Convert ImageProxy to Bitmap val binaryBitmap = createBinaryBitmap(bitmap) val reader = MultiFormatReader() try { @@ -32,8 +30,6 @@ object BarcodeScanner { cb(null, null) } catch (e: ReaderException) { cb(null, null) - } finally { - imageProxy.close() } } @@ -45,13 +41,21 @@ object BarcodeScanner { return BinaryBitmap(HybridBinarizer(source)) } - fun getAnalysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis { + fun analysisUseCase(cb: (String?, String?) -> Unit): ImageAnalysis { val analysisUseCase = ImageAnalysis.Builder().build() analysisUseCase.setAnalyzer( Executors.newSingleThreadExecutor() ) { imageProxy -> - processImageProxy(imageProxy, cb) + val bitmap = imageProxy.toBitmap() + imageProxy.close() + bitmapUseCase(bitmap, cb) } return analysisUseCase } + + fun bitmapUseCase(bitmap: Bitmap, cb: (String?, String?) -> Unit) { + processImage(bitmap, cb) + } + + } \ No newline at end of file diff --git a/app/src/main/java/net/helcel/fidelity/tools/ErrorToaster.kt b/app/src/main/java/net/helcel/fidelity/tools/ErrorToaster.kt index 70a754f..4973cc7 100644 --- a/app/src/main/java/net/helcel/fidelity/tools/ErrorToaster.kt +++ b/app/src/main/java/net/helcel/fidelity/tools/ErrorToaster.kt @@ -20,4 +20,12 @@ object ErrorToaster { fun invalidFormat(activity: Context?) { helper(activity, "Invalid Format", Toast.LENGTH_SHORT) } + + fun nothingFound(activity: Context?) { + helper(activity, "Nothing Found", Toast.LENGTH_SHORT) + } + + fun noPermission(activity: Context?) { + helper(activity, "Missing Permission", Toast.LENGTH_LONG) + } } diff --git a/app/src/main/res/drawable/open.xml b/app/src/main/res/drawable/open.xml new file mode 100644 index 0000000..9d82c42 --- /dev/null +++ b/app/src/main/res/drawable/open.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/layout/frag_launcher.xml b/app/src/main/res/layout/frag_launcher.xml index c23c101..30a1cae 100644 --- a/app/src/main/res/layout/frag_launcher.xml +++ b/app/src/main/res/layout/frag_launcher.xml @@ -56,6 +56,16 @@ app:maxImageSize="32dp" app:srcCompat="@drawable/camera" /> + + Code Format Save + Open CODE_39 CODE_93 diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt new file mode 100644 index 0000000..eb5b650 --- /dev/null +++ b/metadata/en-US/full_description.txt @@ -0,0 +1 @@ +

Keepass-Fidelity adds an interface to view/save barcodes (QR included) to Keepass through the plugin interface of the Keepass2Android app.


  • Launcher: view and launch recent entries (a per entry flag can disable this behaviour)
  • View: view entries from the history or queried from Keepass2Android
  • Create: add entries from the camera, an image of by filling out a form. The entry is then created in the Keepass2Android app
  • Data: the app uses the following data Title (entry name), barcode type (QR, UPC, ...), barcode content (number/text content) and a "secure" flag (enable/disable caching the entry).
\ No newline at end of file diff --git a/metadata/en-US/images/icon.png b/metadata/en-US/images/icon.png new file mode 100644 index 0000000..34087a0 Binary files /dev/null and b/metadata/en-US/images/icon.png differ diff --git a/.github/images/edit.jpg b/metadata/en-US/images/phoneScreenshots/edit.jpg similarity index 100% rename from .github/images/edit.jpg rename to metadata/en-US/images/phoneScreenshots/edit.jpg diff --git a/.github/images/launcher.jpg b/metadata/en-US/images/phoneScreenshots/launcher.jpg similarity index 100% rename from .github/images/launcher.jpg rename to metadata/en-US/images/phoneScreenshots/launcher.jpg diff --git a/.github/images/view.jpg b/metadata/en-US/images/phoneScreenshots/view.jpg similarity index 100% rename from .github/images/view.jpg rename to metadata/en-US/images/phoneScreenshots/view.jpg diff --git a/metadata/en-US/short_description.txt b/metadata/en-US/short_description.txt new file mode 100644 index 0000000..408c2f1 --- /dev/null +++ b/metadata/en-US/short_description.txt @@ -0,0 +1 @@ +Fidelity (Membership/Loyalty) Card plugin for Keepass2Android \ No newline at end of file