Show certificates QrCode

This commit is contained in:
Pierre HUBERT 2022-02-16 12:09:20 +01:00
parent 2b6fe21792
commit afa65c8239
18 changed files with 224 additions and 11 deletions

View File

@ -12,6 +12,7 @@
<entry key="app/src/main/res/layout/content_certs.xml" value="0.29483695652173914" /> <entry key="app/src/main/res/layout/content_certs.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/fragment_first.xml" value="0.29483695652173914" /> <entry key="app/src/main/res/layout/fragment_first.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/fragment_second.xml" value="0.29483695652173914" /> <entry key="app/src/main/res/layout/fragment_second.xml" value="0.29483695652173914" />
<entry key="app/src/main/res/layout/qr_entry.xml" value="0.28125" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3859375" /> <entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3859375" />
</map> </map>
</option> </option>

View File

@ -2,3 +2,6 @@ package org.communiquons.dccaggregator
// Official URL provided to add certificate to wallet // Official URL provided to add certificate to wallet
const val WALLET_URL = "https://bonjour.tousanticovid.gouv.fr/app/walletdcc#" const val WALLET_URL = "https://bonjour.tousanticovid.gouv.fr/app/walletdcc#"
// Name of the preferences file holding certificates list
const val PREF_CERTS_LIST = "certs_list"

View File

@ -0,0 +1,8 @@
package org.communiquons.dccaggregator.activities
import androidx.appcompat.app.AppCompatActivity
import org.communiquons.dccaggregator.DCCAggregator
abstract class BaseActivity : AppCompatActivity() {
val certsManager get() = (application as DCCAggregator).certManager
}

View File

@ -1,23 +1,30 @@
package org.communiquons.dccaggregator.activities package org.communiquons.dccaggregator.activities
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
import org.communiquons.dccaggregator.R import org.communiquons.dccaggregator.R
import org.communiquons.dccaggregator.WALLET_URL import org.communiquons.dccaggregator.WALLET_URL
import org.communiquons.dccaggregator.adapters.CertificatesAdapter
import org.communiquons.dccaggregator.databinding.ActivityCertsManagerBinding import org.communiquons.dccaggregator.databinding.ActivityCertsManagerBinding
import org.communiquons.dccaggregator.models.Certificate import org.communiquons.dccaggregator.models.Certificate
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
class CertsManager : AppCompatActivity() { class CertsManager : BaseActivity() {
private lateinit var binding: ActivityCertsManagerBinding private lateinit var binding: ActivityCertsManagerBinding
private val certsList by lazy {
ArrayList(certsManager.certsList)
}
private val listAdapter by lazy {
CertificatesAdapter(this, certsList)
}
private val TAG = CertsManager::class.java.canonicalName private val TAG = CertsManager::class.java.canonicalName
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -27,15 +34,19 @@ class CertsManager : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Initialize certificates list
binding.certsList.adapter = listAdapter
binding.scanCertButton.setOnClickListener { binding.scanCertButton.setOnClickListener {
addNewCertificate() addNewCertificate()
} }
intent?.dataString.toString().apply { intent?.dataString.toString().apply {
if (this.startsWith(WALLET_URL)) if (this.startsWith(WALLET_URL))
processGivenCertificate(this) processGivenCertificate(this)
} }
refreshList()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -71,14 +82,17 @@ class CertsManager : AppCompatActivity() {
} }
try { try {
val cert = Certificate(decoded) certsManager.addCertificate(Certificate(decoded))
Log.v(TAG, cert.fullName)
refreshList()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
Toast.makeText(this, R.string.invalid_certificate, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.invalid_certificate, Toast.LENGTH_SHORT).show()
return return
} }
} }
private fun refreshList() {
}
} }

View File

@ -7,7 +7,7 @@ import androidx.appcompat.app.AppCompatDelegate
import org.communiquons.dccaggregator.DCCAggregator import org.communiquons.dccaggregator.DCCAggregator
import org.communiquons.dccaggregator.R import org.communiquons.dccaggregator.R
class MainActivity : AppCompatActivity() { class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setTheme(R.style.Theme_DCCAggregator) setTheme(R.style.Theme_DCCAggregator)

View File

@ -0,0 +1,36 @@
package org.communiquons.dccaggregator.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.BarcodeEncoder
import org.communiquons.dccaggregator.R
import org.communiquons.dccaggregator.ext.toDimensSize
import org.communiquons.dccaggregator.models.Certificate
class CertificatesAdapter(cxt: Context, list: ArrayList<Certificate>) :
ArrayAdapter<Certificate>(cxt, 0, list) {
private val qrSize by lazy {
R.dimen.qr_code_widget_size.toDimensSize(context).toInt()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view =
convertView ?: LayoutInflater.from(context).inflate(R.layout.qr_entry, parent, false)
val cert = getItem(position) as Certificate
// Generate Qr code
val bitmap =
BarcodeEncoder().encodeBitmap(cert.encoded, BarcodeFormat.QR_CODE, qrSize, qrSize)
view.findViewById<ImageView>(R.id.certQrCode).setImageBitmap(bitmap)
return view
}
}

View File

@ -0,0 +1,14 @@
package org.communiquons.dccaggregator.ext
import android.content.Context
import androidx.annotation.DimenRes
import androidx.annotation.Dimension
/**
* Return the dimension corresponding to the current theme of the receiver id.
*
* @param context context where the theme is.
* @return Resource dimension value multiplied by the appropriate metric.
*/
@Dimension
fun @receiver:DimenRes Int.toDimensSize(context: Context): Float = context.resources.getDimension(this)

View File

@ -2,9 +2,37 @@ package org.communiquons.dccaggregator.helper
import android.content.Context import android.content.Context
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.communiquons.dccaggregator.PREF_CERTS_LIST
import org.communiquons.dccaggregator.models.Certificate
class CertificatesList(context: Context) { class CertificatesList(context: Context) {
private val prefsManager = PreferenceManager.getDefaultSharedPreferences(context) private val prefsManager = PreferenceManager.getDefaultSharedPreferences(context)
val hasGeneratedCertificate get() = false val hasGeneratedCertificate get() = false
/**
* The list of certificates, as a strings list
*/
private var listString
get() = prefsManager.getStringSet(PREF_CERTS_LIST, HashSet())!!
set(v) = prefsManager.edit().putStringSet(PREF_CERTS_LIST, v).apply()
/**
* The list of decoded certificates
*/
val certsList get() = listString.map { t -> Certificate(t) }
/**
* Check if a certificate is already present in the list or not
*/
fun hasCertificate(cert: Certificate): Boolean {
return certsList.any { it.id == cert.id }
}
/**
* Add a certificate to the list
*/
fun addCertificate(cert: Certificate) {
listString = listString.apply { add(cert.encoded) }
}
} }

View File

@ -9,7 +9,7 @@ import dgca.verifier.app.decoder.model.GreenCertificate
class Certificate(val encoded: String) { class Certificate(val encoded: String) {
// Decoded green certificate // Decoded green certificate
val decoded: GreenCertificate; val decoded: GreenCertificate
init { init {
val res = DefaultCertificateDecoder(Base45Decoder()).decodeCertificate(encoded) val res = DefaultCertificateDecoder(Base45Decoder()).decodeCertificate(encoded)
@ -24,4 +24,9 @@ class Certificate(val encoded: String) {
* Get person full name * Get person full name
*/ */
val fullName get() = "${decoded.person.standardisedGivenName} ${decoded.person.standardisedFamilyName}" val fullName get() = "${decoded.person.standardisedGivenName} ${decoded.person.standardisedFamilyName}"
/**
* Get certificate id
*/
val id get() = decoded.getDgci()
} }

View File

@ -0,0 +1,41 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

View File

@ -18,4 +18,13 @@
android:src="@android:drawable/ic_input_add" android:src="@android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<ListView
android:id="@+id/certsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="150dp"
android:orientation="horizontal"
android:padding="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="1dp">
<ImageView
android:id="@+id/certQrCode"
android:layout_width="110dp"
android:layout_height="match_parent"
android:contentDescription="@string/certificate_qr_code"
app:srcCompat="@drawable/ic_qr" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
tools:text="John Doe" />
<TextView
android:id="@+id/date_of_birth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="italic"
tools:text="01/01/1995" />
<TextView
android:id="@+id/certificate_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="A long long text about the certificate that explains what kind of certificate it is" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="qr_code_widget_dcc_size">200dp</dimen>
<dimen name="qr_code_widget_size">110dp</dimen>
</resources>

View File

@ -5,4 +5,5 @@
<string name="scan_qr_prompt">Please present the QrCode of the certificate you want to add</string> <string name="scan_qr_prompt">Please present the QrCode of the certificate you want to add</string>
<string name="action_import_certificate">Import certificate</string> <string name="action_import_certificate">Import certificate</string>
<string name="invalid_certificate">The given certificate could not be decoded!</string> <string name="invalid_certificate">The given certificate could not be decoded!</string>
<string name="certificate_qr_code">Certificate QR Code</string>
</resources> </resources>