Commit 1a0abc92 authored by Hyene's avatar Hyene
Browse files

Maj 1.5.3 / build 47

parent 0cff8c6c
......@@ -18,6 +18,13 @@ Cette application dispose de deux modes de fonctionnement :
* Un mode détaillé qui affiche plus de données correspondant aux éléments contenus dans le pass sanitaire (2D DOC ou QR européen) et réservé aux catégories de personnes dûment habilitées au titre de la réglementation en vigueur.
Modalité de publication
-------------------
Le code publié contient l'ensemble des règles de gestion des modes lite et mode détaillé.
L'activation du mode détaillé est caviardé
Applications & roadmap
-------------------
......@@ -29,6 +36,7 @@ Les applications vont évoluer avec, entre autres, deux points déjà identifié
* Le remplacement du service AKAMAI par un service équivalent issue d'une entité européenne
Licences
-------------------
......
package com.ingroupe.verify.anticovid.auth
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import com.ingroupe.verify.anticovid.common.Constants
import java.util.*
class JWTUtils {
companion object {
const val TAG = "jwtUtils"
fun resetPrefsForToken(sharedPref: SharedPreferences) {
with(sharedPref.edit()) {
putString(
Constants.SavedItems.CONF_NAME.text,
Constants.CONF_BASIC
)
putLong(
Constants.SavedItems.CONF_DATE_EXP.text,
0
)
remove(Constants.SavedItems.CURRENT_TOKEN.text)
putBoolean(
Constants.SavedItems.SHOW_RESULT_TUTO.text,
true
)
apply()
}
}
fun isModeOT(context: Context): Boolean {
val sharedPref = (context as Activity).getPreferences(Context.MODE_PRIVATE)
return sharedPref?.getString(Constants.SavedItems.CURRENT_TOKEN.text, null) != null
}
fun daysBeforeExpiration(context: Context): Int {
val dateExp = getDateExpiration(context)
val nbDaysBeforeExp = (dateExp - Date().time) / (1000*60*60*24)
return if(nbDaysBeforeExp < 0) {
0
} else {
nbDaysBeforeExp.toInt()
}
}
fun hoursBeforeExpiration(context: Context): Int {
val dateExp = getDateExpiration(context)
val nbHoursBeforeExp = (dateExp - Date().time) / (1000*60*60)
return if(nbHoursBeforeExp < 0) {
0
} else {
nbHoursBeforeExp.toInt()
}
}
private fun getDateExpiration(context: Context) : Long {
val sharedPref = (context as Activity).getPreferences(Context.MODE_PRIVATE)
return sharedPref?.getLong(Constants.SavedItems.CONF_DATE_EXP.text, 0) ?: 0
}
}
}
\ No newline at end of file
package com.ingroupe.verify.anticovid.ui.scan
import android.app.Activity
import android.content.Context
import android.hardware.Camera
import android.os.Bundle
import android.util.Log
import android.view.*
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ContextThemeWrapper
import androidx.lifecycle.ViewModelProvider
import com.google.mlkit.vision.barcode.Barcode
import com.ingroupe.verify.anticovid.MainActivity
import com.ingroupe.verify.anticovid.R
import com.ingroupe.verify.anticovid.auth.JWTUtils
import com.ingroupe.verify.anticovid.common.*
import com.ingroupe.verify.anticovid.camera.mlkit.BarcodeScannerProcessor
import com.ingroupe.verify.anticovid.camera.mlkit.CameraSource
import com.ingroupe.verify.anticovid.camera.mlkit.CameraSourcePreview
import com.ingroupe.verify.anticovid.camera.mlkit.GraphicOverlay
import com.ingroupe.verify.anticovid.databinding.PopupOtModeActivatedBinding
import com.ingroupe.verify.anticovid.databinding.ScanMainBinding
import com.ingroupe.verify.anticovid.service.document.model.DocumentResult
import com.ingroupe.verify.anticovid.ui.actionchoice.ActionChoiceChildFragment
import com.ingroupe.verify.anticovid.ui.result.ResultScanChildFragment
import com.ingroupe.verify.anticovid.ui.tutorialresult.ot.TutorialResultOTChildFragment
import com.ingroupe.verify.anticovid.ui.tutorialresult.lite.TutorialResultLiteChildFragment
import com.ingroupe.verify.anticovid.ui.tutorialscan2ddoc.TutorialScan2DDocHelpChildFragment
import java.io.IOException
import java.util.*
import kotlin.math.sqrt
class ScanChildFragment : FeatureChildFragment(), ScanView, View.OnTouchListener {
override fun getTitle(): String = "Scan du 2D-Doc"
override fun getTitleId(): Int = R.string.title_scan
companion object {
const val TAG = "Scan"
fun newInstance() = ScanChildFragment()
}
private var _binding: ScanMainBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var presenter: ScanPresenter? = null
private var cameraSource: CameraSource? = null
private var preview: CameraSourcePreview? = null
private var graphicOverlay: GraphicOverlay? = null
//Flash
private var flash = false
//Zooming
private var fingerSpacing = 0F
private lateinit var model: SharedViewModel
private var isCallingWS = false
private var isShowingPopup = false
private var isFirstFrame = true
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = ScanMainBinding.inflate(inflater, container, false)
val view = binding.root
if (presenter == null) {
presenter = ScanPresenterImpl(this)
}
preview = binding.previewView
if (preview == null) {
Log.d(TAG, "Preview is null")
}
graphicOverlay = binding.graphicOverlay
if (graphicOverlay == null) {
Log.d(TAG, "graphicOverlay is null")
}
createCameraSource()
return view
}
@Suppress("DEPRECATION")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isCallingWS = false
binding.imageViewFlash.setOnClickListener {
if (cameraSource?.isFlashSupported != true) {
Utils.showToast(
activity as Activity,
getString(R.string.scan_toast_flash_not_supported)
)
} else {
flash = if (flash) {
binding.imageViewFlash.setBackgroundResource(R.drawable.button_flash_off)
cameraSource?.updateFlashMode(Camera.Parameters.FLASH_MODE_OFF)
false
} else {
binding.imageViewFlash.setBackgroundResource(R.drawable.button_flash_on)
cameraSource?.updateFlashMode(Camera.Parameters.FLASH_MODE_TORCH)
true
}
}
}
}
override fun createOptionsMenu(menu: Menu) {
activity?.menuInflater?.inflate(R.menu.menu_scan_2ddoc, menu)
}
override fun showNavigation(): MainActivity.NavigationIcon {
return MainActivity.NavigationIcon.BACK
}
@Synchronized
private fun createCameraSource() {
// If there's no existing cameraSource, create one.
if (cameraSource == null) {
cameraSource = CameraSource(activity, graphicOverlay)
}
try {
context?.let {
cameraSource!!.setMachineLearningFrameProcessor(
BarcodeScannerProcessor(this, it)
)
}
} catch (e: Exception) {
Toast.makeText(
context, "Can not create image processor: " + e.message,
Toast.LENGTH_LONG
).show()
}
}
/**
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
* (e.g., because onResume was called before the camera source was created), this will be called
* again when the camera source is created.
*/
@Synchronized
private fun startCameraSource() {
if (cameraSource != null) {
try {
if (preview == null) {
Log.d(TAG, "resume: Preview is null")
}
if (graphicOverlay == null) {
Log.d(TAG, "resume: graphOverlay is null")
}
preview!!.start(cameraSource, graphicOverlay)
} catch (e: IOException) {
Log.e(TAG, "Unable to start camera source.", e)
cameraSource!!.release()
cameraSource = null
}
}
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
isCallingWS = false
model = activity?.run {
ViewModelProvider(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.currentResponse = null
model.currentResponseDate = null
isFirstFrame = true
createCameraSource()
startCameraSource()
binding.previewView.setOnTouchListener(this)
}
/** Stops the camera. */
override fun onPause() {
super.onPause()
preview?.stop()
}
override fun onDestroy() {
super.onDestroy()
preview?.release()
if (cameraSource != null) {
cameraSource?.release()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_help -> {
goToHelp()
return true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun showResult(response: DocumentResult) {
model = activity?.run {
ViewModelProvider(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.currentSearch = null
model.currentResponse = response
model.currentResponseDate = Date()
model.resultAlreadyViewed = false
goToResult()
}
override fun showErrorMessage(title: Int, message: Int) {
Log.e(TAG, "showErrorMessage")
isCallingWS = false
preview?.stop()
this.context?.let { context ->
val dialog = AlertDialog.Builder(context).setTitle(getString(title)).setMessage(
getString(
message
)
)
.setNegativeButton("OK") { dialog, _ -> dialog.dismiss()
startCameraSource()
}.create()
dialog.show()
}
}
private fun goToResult() {
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
val showTuto = sharedPref?.getBoolean(Constants.SavedItems.SHOW_RESULT_TUTO.text, true)
if (showTuto == true) {
val isModeOT = context?.let { JWTUtils.isModeOT(it) }
if(isModeOT == true) {
featureFragment?.replaceFragment(TutorialResultOTChildFragment.TAG)
} else {
featureFragment?.replaceFragment(TutorialResultLiteChildFragment.TAG)
}
} else {
featureFragment?.replaceFragment(ResultScanChildFragment.TAG)
}
}
private fun goToHelp() {
featureFragment?.replaceFragment(TutorialScan2DDocHelpChildFragment.TAG)
}
override fun barcodeResult(barcode: Barcode) {
if(isFirstFrame) {
// pour éviter de récuper un résultat en mémoire
isFirstFrame = false
} else if (barcode.rawValue != null && !isCallingWS && !isShowingPopup) {
if (barcode.format != Barcode.FORMAT_DATA_MATRIX) {
isCallingWS = true
if (barcode.format == Barcode.FORMAT_QR_CODE && barcode.rawValue != null) {
context?.let { c ->
val dcc = presenter?.checkDcc(barcode.rawValue!!, c)
if(dcc != null) {
showResult(dcc)
return
}
}
}
isCallingWS = false
Utils.showToast(activity as Activity, getString(R.string.scan_not_2ddoc))
} else if (barcode.rawValue!!.length>=23
&& barcode.rawValue!!.subSequence(20, 22) != "B2"
&& barcode.rawValue!!.subSequence(20, 22) != "L1") {
isCallingWS = false
Utils.showToast(activity as Activity, getString(R.string.scan_not_2ddoc_B2_L1))
} else {
model = activity?.run {
ViewModelProvider(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
var isExpired = false
context?.let { c ->
isExpired = presenter?.isExpired(model, c) ?: false
}
if(isExpired) {
val dialog = AlertDialog.Builder(requireContext())
.setCancelable(false)
.setTitle(getString(R.string.popup_licence_expired_title))
.setMessage(getString(R.string.popup_licence_expired_text))
.setPositiveButton(getString(R.string.action_ok)) { dialog, _ ->
dialog.dismiss()
presenter?.on2dDocDetected(
requireContext(),
barcode.rawValue!!
)
}
.create()
dialog.show()
} else {
presenter?.on2dDocDetected(
requireContext(),
barcode.rawValue!!
)
}
}
}
}
@Suppress("DEPRECATION")
override fun onTouch(v: View, event: MotionEvent): Boolean {
try {
val currentFingerSpacing: Float
if (event.pointerCount == 2) { //Multi touch.
currentFingerSpacing = getFingerSpacing(event)
if (fingerSpacing != 0F) {
if (currentFingerSpacing > fingerSpacing) {
cameraSource?.zoomIn()
} else if (currentFingerSpacing < fingerSpacing) {
cameraSource?.zoomOut()
}
}
fingerSpacing = currentFingerSpacing
}
return true
} catch (e: Exception) {
//Error handling up to you
return true
}
}
private fun getFingerSpacing(event: MotionEvent): Float {
val x = event.getX(0) - event.getX(1)
val y = event.getY(0) - event.getY(1)
return sqrt(x * x + y * y)
}
}
\ No newline at end of file
package com.ingroupe.verify.anticovid.ui.scan
import android.content.Context
import com.ingroupe.verify.anticovid.common.SharedViewModel
import com.ingroupe.verify.anticovid.service.document.model.DocumentResult
interface ScanPresenter {
fun on2dDocDetected(context: Context, result2DDoc: String)
fun isExpired(model: SharedViewModel, context: Context): Boolean
fun checkDcc(qrCode: String, context: Context) : DocumentResult?
}
\ No newline at end of file
package com.ingroupe.verify.anticovid.ui.scan
import android.app.Activity
import android.content.Context
import android.util.Log
import com.ingroupe.verify.anticovid.R
import com.ingroupe.verify.anticovid.auth.JWTUtils
import com.ingroupe.verify.anticovid.common.Constants
import com.ingroupe.verify.anticovid.common.SharedViewModel
import com.ingroupe.verify.anticovid.common.Utils
import com.ingroupe.verify.anticovid.service.api.configuration.ConfResult
import com.ingroupe.verify.anticovid.service.document.DocOfflineService
import com.ingroupe.verify.anticovid.service.document.model.DocumentResult
import java.util.*
class ScanPresenterImpl(
private val view: ScanView
) : ScanPresenter {
companion object {
const val TAG = "Scan2DDocP"
}
override fun on2dDocDetected(context: Context, result2DDoc: String) {
try {
view.showResult(DocOfflineService.getDocumentBy2DDoc(result2DDoc ,context))
} catch (e: Exception) {
Log.e(TAG, "KO :", e)
view.showErrorMessage(R.string.error_call_error_title, R.string.error_call_http_ko)
}
}
override fun isExpired(model: SharedViewModel, context: Context): Boolean {
var isExpired = false
val sharedPref = (context as Activity).getPreferences(Context.MODE_PRIVATE)
var confName = sharedPref.getString(Constants.SavedItems.CONF_NAME.text, Constants.CONF_BASIC) ?: Constants.CONF_BASIC
val dateExp = Date(sharedPref.getLong(Constants.SavedItems.CONF_DATE_EXP.text, 0))
if(confName != Constants.CONF_BASIC && dateExp.before(Date())) {
confName = Constants.CONF_BASIC
JWTUtils.resetPrefsForToken(sharedPref)
isExpired = true
}
model.configuration?. let { conf ->
conf.types?.clear()
val specificConf : ConfResult? = Utils.loadFromAsset(context, confName)
specificConf?.types?.let {
conf.types = it
}
}
return isExpired
}
override fun checkDcc(qrCode: String, context: Context) : DocumentResult? {
return try {
DocOfflineService.getDocumentByDcc(qrCode, context)
} catch (e: Exception) {
null
}
}
}
\ No newline at end of file
<resources>
<string name="title_information">Information and confidentiality</string>
<string name="title_init">Initialization</string>
<string name="title_settings">Settings</string>
<string name="title_tutorial_scan">2D-DOC / QR Code Scan</string>
<string name="title_tutorial_scan_help">2D-DOC / QR Code Scan</string>
<string name="title_action_choice">Verification</string>
<string name="title_result">Result</string>
<string name="title_scan">2D-DOC / QR Code Scan</string>
<string name="title_tutorial_result">Result - Tutorial</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="init_title_app_name">TousAntiCovid Verif</string>
<string name="nav_header_title">TousAntiCovid Verif</string>
<string name="nav_header_desc">Certificates COVID 2D-DOC and QR Code control application</string>
<string name="action_scan_2ddoc">Scan the 2D-DOC or the QR Code</string>
<string name="action_scan_ot_mode">Detailed Mode for Transport Operator</string>
<string name="action_ot_expiration_text1">The transport operator mode will expire in %1$d days.</string>
<string name="action_ot_expiration_text1_hours">The transport operator mode will expire in %1$d hours.</string>
<string name="action_ot_expiration_text2"> </string>
<string name="scan_not_2ddoc">The barcode was not recognized. Please check if it is a 2D-DOC or a QR Code and retry to scan.</string>
<string name="scan_not_2ddoc_B2_L1">The scanned 2D-DOC / QR Code is not a Covid-19 certificate</string>
<string name="scan_instruction">Set the 2D-DOC / QR Code in the middle of the screen, then move back your phone until the scan trigger.</string>
<string name="scan_image_flash">flash</string>
<string name="scan_toast_flash_not_supported">Flash mode not supported</string>
<string name="tuto_scan_title">How to scan the 2D-DOC / QR Code ?</string>
<string name="tuto_scan_text1">Set the 2D-DOC / QR Code in the middle of the screen, then move back your phone until the scan trigger.</string>
<string name="tuto_scan_text2">The 2D-DOC / QR Code image should be sharp to trigger the scan.</string>
<string name="tuto_result_lite_title">How to interpret results ?</string>
<string name="tuto_result_lite_text1">The top part of the screen shows the global result of the verification :</string>