Commit 318760b6 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 2.3.4

- New attestations with 2 motif lists
- Replace Signal images
- Fix assets not loaded on older API
- Don't save corrupted json from server
parent 755987df
......@@ -12,7 +12,7 @@ package com.lunabeestudio.stopcovid.coreui
object ConfigConstant {
private const val BASE_URL: String = "https://app.stopcovid.gouv.fr/"
private const val VERSION_PATH: String = "json/version-28/"
private const val VERSION_PATH: String = "json/version-29/"
private const val VERSIONED_SERVER_URL: String = BASE_URL + VERSION_PATH
const val SERVER_CERTIFICATE_SHA256: String = "sha256/sXQojvwsiyblrpMQIVRXGC5u7AgknzTJm+VIK1kQmD8="
......
......@@ -14,13 +14,14 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.lunabeestudio.stopcovid.coreui.extension.stringsFormat
import com.lunabeestudio.stopcovid.coreui.manager.LocalizedStrings
import com.lunabeestudio.stopcovid.coreui.manager.StringsManager
abstract class BaseFragment : Fragment() {
abstract fun refreshScreen()
val strings: HashMap<String, String>
val strings: LocalizedStrings
get() = StringsManager.strings
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......
......@@ -14,6 +14,7 @@ import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.lunabeestudio.stopcovid.coreui.BuildConfig
import com.lunabeestudio.stopcovid.coreui.UiConstants
import com.lunabeestudio.stopcovid.coreui.extension.getFirstSupportedLanguage
......@@ -25,7 +26,7 @@ import java.io.File
import java.lang.reflect.Type
import kotlin.math.abs
abstract class ServerManager {
abstract class ServerManager<T> {
private var gson: Gson = Gson()
......@@ -46,7 +47,7 @@ abstract class ServerManager {
}
}
protected suspend fun <T> loadLocal(context: Context): T? {
protected suspend fun loadLocal(context: Context): T? {
val currentLanguage = context.getFirstSupportedLanguage()
return loadFromFiles(context, currentLanguage)
......@@ -54,18 +55,24 @@ abstract class ServerManager {
}
private suspend fun fetchLast(context: Context, languageCode: String): Boolean {
val filename = "$prefix$languageCode$extension"
val tmpFile = File(context.filesDir, "$filename.bck")
return try {
val filename = "$prefix$languageCode$extension"
"$url$filename".saveTo(context, File(context.filesDir, filename))
saveLastRefresh(context)
true
"$url$filename".saveTo(context, tmpFile)
if (fileNotCorrupted(tmpFile)) {
tmpFile.copyTo(File(context.filesDir, filename), overwrite = true, bufferSize = 4 * 1024)
saveLastRefresh(context)
true
} else {
false
}
} catch (e: Exception) {
Timber.e(e, "Fetching fail for $languageCode")
false
}
}
private suspend fun <T> loadFromFiles(context: Context, languageCode: String): T? {
private suspend fun loadFromFiles(context: Context, languageCode: String): T? {
val fileName = "$prefix$languageCode$extension"
val file = File(context.filesDir, fileName)
return if (!file.exists()) {
......@@ -84,11 +91,12 @@ abstract class ServerManager {
}
}
private suspend fun <T> loadFromAssets(context: Context, languageCode: String): T? {
private suspend fun loadFromAssets(context: Context, languageCode: String): T? {
val fileName = "$prefix$languageCode$extension"
@Suppress("BlockingMethodInNonBlockingContext")
return withContext(Dispatchers.IO) {
if (context.assets.list(folderName)?.contains(fileName) != true) {
// Remove suffix to fix list asset issue with older API
if (context.assets.list(folderName.removeSuffix("/"))?.contains(fileName) != true) {
null
} else {
gson.fromJson<T>(context.assets.open("$folderName$fileName").use {
......@@ -110,6 +118,18 @@ abstract class ServerManager {
}
}
private suspend fun fileNotCorrupted(file: File): Boolean {
return withContext(Dispatchers.IO) {
try {
gson.fromJson<T>(transform(file.readText()), type)
true
} catch (e: JsonSyntaxException) {
Timber.e(e, "Fetched corrupted file ${file.name}")
false
}
}
}
fun clearLocal(context: Context) {
val filename = "$prefix${context.getFirstSupportedLanguage()}$extension"
File(context.filesDir, filename).delete()
......
......@@ -21,9 +21,11 @@ import com.lunabeestudio.stopcovid.coreui.extension.fixFormatter
import com.lunabeestudio.stopcovid.coreui.extension.getFirstSupportedLanguage
import java.lang.reflect.Type
object StringsManager : ServerManager() {
typealias LocalizedStrings = HashMap<String, String>
var strings: HashMap<String, String> = hashMapOf()
object StringsManager : ServerManager<LocalizedStrings>() {
var strings: LocalizedStrings = hashMapOf()
private set(value) {
if (field != value) {
_liveStrings.postValue(Event(value))
......@@ -31,15 +33,15 @@ object StringsManager : ServerManager() {
field = value
}
private val _liveStrings: MutableLiveData<Event<HashMap<String, String>>> = MutableLiveData()
val liveStrings: LiveData<Event<HashMap<String, String>>>
private val _liveStrings: MutableLiveData<Event<LocalizedStrings>> = MutableLiveData()
val liveStrings: LiveData<Event<LocalizedStrings>>
get() = _liveStrings
private var prevLanguage: String? = null
suspend fun initialize(context: Context) {
prevLanguage = context.getFirstSupportedLanguage()
loadLocal<HashMap<String, String>>(context)?.let {
loadLocal(context)?.let {
strings = it
}
}
......@@ -49,14 +51,14 @@ object StringsManager : ServerManager() {
val languageHasChanged = prevLanguage != newLanguage
if (languageHasChanged) {
loadLocal<HashMap<String, String>>(context)?.let {
loadLocal(context)?.let {
strings = it
}
}
val hasFetch = fetchLast(context, languageHasChanged)
if (hasFetch) {
loadLocal<HashMap<String, String>>(context)?.let {
loadLocal(context)?.let {
prevLanguage = newLanguage
strings = it
}
......@@ -66,7 +68,7 @@ object StringsManager : ServerManager() {
override val url: String = ConfigConstant.Labels.URL
override val folderName: String = ConfigConstant.Labels.FOLDER
override val prefix: String = ConfigConstant.Labels.FILE_PREFIX
override val type: Type = object : TypeToken<HashMap<String, String>>() {}.type
override val type: Type = object : TypeToken<LocalizedStrings>() {}.type
override val lastRefreshSharedPrefsKey: String = UiConstants.SharePrefs.LAST_STRINGS_REFRESH
override fun transform(input: String): String = input.fixFormatter()
}
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2021/22/03 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.domain.model
class Attestation(
val qrCode: String,
val footer: String,
val qrCodeString: String,
val timestamp: Long,
val reason: String
)
\ No newline at end of file
......@@ -12,5 +12,6 @@ package com.lunabeestudio.domain.model
data class FormEntry(
val value: String?,
val type: String
val type: String,
val key: String
)
\ No newline at end of file
......@@ -17,6 +17,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.lunabeestudio.domain.model.Attestation
import com.lunabeestudio.domain.model.Calibration
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.FormEntry
......@@ -116,16 +117,23 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
get() = getEncryptedValue(SHARED_PREF_KEY_SAVED_ATTESTATION_DATA, object : TypeToken<Map<String, FormEntry>>() {}.type)
set(value) = setEncryptedValue(SHARED_PREF_KEY_SAVED_ATTESTATION_DATA, value)
private var _attestationsLiveData: MutableLiveData<List<Map<String, FormEntry>>?> = MutableLiveData(attestations)
override val attestationsLiveData: LiveData<List<Map<String, FormEntry>>?>
private var _attestationsLiveData: MutableLiveData<List<Attestation>?> = MutableLiveData(attestations)
override val attestationsLiveData: LiveData<List<Attestation>?>
get() = _attestationsLiveData
override var attestations: List<Map<String, FormEntry>>?
get() = getEncryptedValue(SHARED_PREF_KEY_ATTESTATIONS, object : TypeToken<List<Map<String, FormEntry>>>() {}.type)
override var attestations: List<Attestation>?
get() = getEncryptedValue(SHARED_PREF_KEY_ATTESTATIONS, object : TypeToken<List<Attestation>>() {}.type)
set(value) {
setEncryptedValue(SHARED_PREF_KEY_ATTESTATIONS, value)
_attestationsLiveData.postValue(value)
}
override var deprecatedAttestations: List<Map<String, FormEntry>>?
get() = getEncryptedValue(SHARED_PREF_KEY_DEPRECTATED_ATTESTATIONS, object : TypeToken<List<Map<String, FormEntry>>>() {}.type)
set(value) {
setEncryptedValue(SHARED_PREF_KEY_DEPRECTATED_ATTESTATIONS, value)
}
override var reportDate: Long?
get() = getAndMigrateOldUnencryptedLong(SHARED_PREF_KEY_REPORT_DATE, SHARED_PREF_KEY_REPORT_DATE_ENCRYPTED)
set(value) = setEncryptedValue(SHARED_PREF_KEY_REPORT_DATE_ENCRYPTED, value)
......@@ -328,7 +336,8 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
private const val SHARED_PREF_KEY_IS_SICK = "shared.pref.is_sick"
private const val SHARED_PREF_KEY_SAVE_ATTESTATION_DATA = "shared.pref.save_attestation_data"
private const val SHARED_PREF_KEY_SAVED_ATTESTATION_DATA = "shared.pref.saved_attestation_data"
private const val SHARED_PREF_KEY_ATTESTATIONS = "shared.pref.attestations"
private const val SHARED_PREF_KEY_ATTESTATIONS = "shared.pref.attestations_v2"
private const val SHARED_PREF_KEY_DEPRECTATED_ATTESTATIONS = "shared.pref.attestations"
private const val SHARED_PREF_KEY_REPORT_DATE = "shared.pref.report_date"
private const val SHARED_PREF_KEY_REPORT_DATE_ENCRYPTED = "shared.pref.report_date_encrypted"
private const val SHARED_PREF_KEY_SAVE_DATA_VENUES_QR_CODE = "shared.pref.venues_qr_code"
......
......@@ -11,6 +11,7 @@
package com.lunabeestudio.robert.datasource
import androidx.lifecycle.LiveData
import com.lunabeestudio.domain.model.Attestation
import com.lunabeestudio.domain.model.Calibration
import com.lunabeestudio.domain.model.Configuration
import com.lunabeestudio.domain.model.FormEntry
......@@ -35,8 +36,9 @@ interface LocalKeystoreDataSource {
var atRiskModelVersion: Int?
var deprecatedLastRiskReceivedDate: Long?
var deprecatedLastExposureTimeframe: Int?
val attestationsLiveData: LiveData<List<Map<String, FormEntry>>?>
var attestations: List<Map<String, FormEntry>>?
val attestationsLiveData: LiveData<List<Attestation>?>
var attestations: List<Attestation>?
var deprecatedAttestations: List<Map<String, FormEntry>>?
var savedAttestationData: Map<String, FormEntry>?
var saveAttestationData: Boolean?
var reportDate: Long?
......
......@@ -43,8 +43,8 @@ android {
applicationId "fr.gouv.android.stopcovid"
minSdkVersion 21
targetSdkVersion 30
versionCode 186
versionName "2.3.3"
versionCode 188
versionName "2.3.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......
......@@ -42,44 +42,76 @@
"type":"datetime"
},
{
"key":"reason",
"key":"reason_curfew",
"dataKey": "reason",
"type":"list",
"items":[
{
"code":"travail"
"code":"reason_curfew.travail"
},
{
"code":"sante"
"code":"reason_curfew.sante"
},
{
"code":"famille"
"code":"reason_curfew.famille"
},
{
"code":"handicap"
"code":"reason_curfew.handicap"
},
{
"code":"judiciaire"
"code":"reason_curfew.judiciaire"
},
{
"code":"missions"
"code":"reason_curfew.missions"
},
{
"code":"transit"
"code":"reason_curfew.transit"
},
{
"code":"animaux"
"code":"reason_curfew.animaux"
}
]
},
{
"key":"reason_lockdown",
"dataKey": "reason",
"type":"list",
"items":[
{
"code":"reason_lockdown.sport"
},
{
"code":"reason_lockdown.achats"
},
{
"code":"reason_lockdown.enfants"
},
{
"code":"reason_lockdown.culte_culturel"
},
{
"code":"reason_lockdown.demarche"
},
{
"code":"reason_lockdown.travail"
},
{
"code":"reason_lockdown.sante"
},
{
"code":"reason_lockdown.famille"
},
{
"code":"courses"
"code":"reason_lockdown.handicap"
},
{
"code":"sport"
"code":"reason_lockdown.judiciaire"
},
{
"code":"rassemblement"
"code":"reason_lockdown.demenagement"
},
{
"code":"demarche"
"code":"reason_lockdown.transit"
}
]
}
......
......@@ -56,8 +56,10 @@ object Constants {
}
object Attestation {
const val KEY_REASON: String = "reason"
const val DATA_KEY_REASON: String = "reason"
const val KEY_DATE_TIME: String = "datetime"
const val KEY_CREATION_DATE: String = "creationDate"
const val KEY_CREATION_HOUR: String = "creationHour"
const val VALUE_REASON_SPORT: String = "sport_animaux"
}
......
......@@ -58,6 +58,7 @@ import com.lunabeestudio.stopcovid.extension.alertRiskLevelChanged
import com.lunabeestudio.stopcovid.extension.hideRiskStatus
import com.lunabeestudio.stopcovid.extension.lastVersionCode
import com.lunabeestudio.stopcovid.manager.AppMaintenanceManager
import com.lunabeestudio.stopcovid.manager.AttestationsManager
import com.lunabeestudio.stopcovid.manager.CalibDataSource
import com.lunabeestudio.stopcovid.manager.ConfigDataSource
import com.lunabeestudio.stopcovid.manager.FormManager
......@@ -176,6 +177,7 @@ class StopCovid : Application(), LifecycleObserver, RobertApplication, Isolation
}
appCoroutineScope.launch {
StringsManager.initialize(this@StopCovid)
migrateAttestationsIfNeeded()
}
appCoroutineScope.launch {
InfoCenterManager.initialize(this@StopCovid)
......@@ -208,6 +210,10 @@ class StopCovid : Application(), LifecycleObserver, RobertApplication, Isolation
sharedPrefs.lastVersionCode = BuildConfig.VERSION_CODE
}
private fun migrateAttestationsIfNeeded() {
AttestationsManager.migrateAttestationsIfNeeded(robertManager, secureKeystoreDataSource, StringsManager.strings)
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onAppResume() {
isAppInForeground = true
......@@ -499,7 +505,7 @@ class StopCovid : Application(), LifecycleObserver, RobertApplication, Isolation
DurationUnit.HOURS,
DurationUnit.MILLISECONDS
)
secureKeystoreDataSource.attestations = secureKeystoreDataSource.attestations?.filter { attestation ->
secureKeystoreDataSource.deprecatedAttestations = secureKeystoreDataSource.deprecatedAttestations?.filter { attestation ->
(attestation["datetime"]?.value?.toLongOrNull() ?: 0L) > expiredMilliSeconds
}
}
......
......@@ -24,6 +24,7 @@ import com.github.razir.progressbutton.showProgress
import com.google.gson.Gson
import com.lunabeestudio.stopcovid.R
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.manager.LocalizedStrings
import com.lunabeestudio.stopcovid.coreui.manager.StringsManager
import com.lunabeestudio.stopcovid.databinding.ActivityAppMaintenanceBinding
import com.lunabeestudio.stopcovid.extension.openInExternalBrowser
......@@ -38,7 +39,7 @@ class AppMaintenanceActivity : AppCompatActivity() {
private lateinit var binding: ActivityAppMaintenanceBinding
private var strings: HashMap<String, String> = StringsManager.strings
private var strings: LocalizedStrings = StringsManager.strings
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment