Commit 72cc2a51 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 3.5.0

- Try MIUI battery optimization first
- Choose app language
- Remove sha256 certificate pinning
- Update DGCA
parent 3878990f
[submodule "dgca-app-core-android"]
path = dgca-app-core-android
url = https://gitlab.inria.fr/stopcovid19/dgca-app-core-android.git
url = https://gitlab.inria.fr/stopcovid19/dgca-app-core-android.git
\ No newline at end of file
......@@ -14,7 +14,6 @@ object ConfigConstant {
private const val BASE_URL: String = "https://app-static.tousanticovid.gouv.fr/"
private const val VERSION_PATH: String = "json/version-34/"
private const val VERSIONED_SERVER_URL: String = BASE_URL + VERSION_PATH
const val SERVER_CERTIFICATE_SHA256: String = "sha256/ckVocY6+T4RvpXWtbqOF45qEvNls4oFWi83BryOQgOk="
object Maintenance {
private const val BASE_URL: String = "https://app.tousanticovid.gouv.fr/"
......
......@@ -10,14 +10,17 @@
package com.lunabeestudio.stopcovid.coreui
import java.util.Locale
object UiConstants {
enum class Permissions {
CAMERA, LOCATION
}
object SharePrefs {
object SharedPrefs {
const val LAST_STRINGS_REFRESH: String = "Last.Strings.Refresh"
const val USER_LANGUAGE: String = "User.Language"
}
enum class Notification(val channelId: String, val notificationId: Int) {
......@@ -33,7 +36,7 @@ object UiConstants {
CERTIFICATE_REMINDER("reminder", 10),
}
val SUPPORTED_LANGUAGE: Array<String> = arrayOf("en", "fr")
val SUPPORTED_LOCALES: Array<Locale> = arrayOf(Locale.UK, Locale.FRANCE)
const val DEFAULT_LANGUAGE: String = "en"
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.lunabeestudio.stopcovid.coreui.UiConstants
import java.util.Locale
......@@ -80,11 +81,13 @@ fun Context.showPermissionRationale(
}
}
fun Context.getFirstSupportedLanguage(): String {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
fun Context.getApplicationLanguage(): String {
val userLanguage = PreferenceManager.getDefaultSharedPreferences(this).userLanguage
val supportedLanguages = UiConstants.SUPPORTED_LOCALES.map { it.language }
return userLanguage ?: if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
val locales = resources.configuration.locales.toList()
locales.firstOrNull { UiConstants.SUPPORTED_LANGUAGE.contains(it.language) }?.language ?: UiConstants.DEFAULT_LANGUAGE
locales.firstOrNull { supportedLanguages.contains(it.language) }?.language ?: UiConstants.DEFAULT_LANGUAGE
} else {
Locale.getDefault().language.takeIf { UiConstants.SUPPORTED_LANGUAGE.contains(it) } ?: UiConstants.DEFAULT_LANGUAGE
Locale.getDefault().language.takeIf { supportedLanguages.contains(it) } ?: UiConstants.DEFAULT_LANGUAGE
}
}
\ 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/19/7 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.stopcovid.coreui.extension
import android.content.SharedPreferences
import androidx.core.content.edit
import com.lunabeestudio.stopcovid.coreui.UiConstants
var SharedPreferences.userLanguage: String?
get() = getString(UiConstants.SharedPrefs.USER_LANGUAGE, null)
set(value) = edit { putString(UiConstants.SharedPrefs.USER_LANGUAGE, value) }
\ No newline at end of file
......@@ -16,7 +16,6 @@ import android.net.Uri
import androidx.core.util.AtomicFile
import androidx.emoji.text.EmojiCompat
import com.lunabeestudio.domain.model.CacheConfig
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.network.OkHttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -32,7 +31,7 @@ import java.util.concurrent.TimeUnit
suspend fun String.saveTo(context: Context, file: File): Boolean {
return withContext(Dispatchers.IO) {
val cacheConfig = CacheConfig(File(context.cacheDir, "http_cache"), 30L * 1024L * 1024L)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, ConfigConstant.SERVER_CERTIFICATE_SHA256, cacheConfig)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, cacheConfig)
val request: Request = Request.Builder().apply {
cacheControl(CacheControl.Builder().maxAge(10, TimeUnit.MINUTES).build())
url(this@saveTo)
......@@ -59,7 +58,7 @@ suspend fun String.saveTo(context: Context, file: File): Boolean {
suspend fun String.saveTo(context: Context, atomicFile: AtomicFile, validData: suspend (data: ByteArray) -> Boolean): Boolean {
return withContext(Dispatchers.IO) {
val cacheConfig = CacheConfig(File(context.cacheDir, "http_cache"), 30L * 1024L * 1024L)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, this@saveTo, ConfigConstant.SERVER_CERTIFICATE_SHA256, cacheConfig)
val okHttpClient = OkHttpClient.getDefaultOKHttpClient(context, cacheConfig)
val request: Request = Request.Builder().apply {
cacheControl(CacheControl.Builder().maxAge(10, TimeUnit.MINUTES).build())
url(this@saveTo)
......@@ -99,7 +98,7 @@ fun String.callPhone(context: Context) {
context.startActivity(callIntent)
}
fun String?.safeEmojiSpanify(): CharSequence? {
fun CharSequence?.safeEmojiSpanify(): CharSequence? {
return try {
EmojiCompat.get().process(this ?: "")
} catch (e: IllegalStateException) {
......
......@@ -4,7 +4,7 @@ import android.view.View
import android.widget.TextView
fun TextView.setTextOrHide(
value: String?,
value: CharSequence?,
ifVisibleBlock: (TextView.() -> Unit)? = null
) {
if (value.isNullOrEmpty()) {
......
......@@ -17,7 +17,7 @@ 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
import com.lunabeestudio.stopcovid.coreui.extension.getApplicationLanguage
import com.lunabeestudio.stopcovid.coreui.extension.saveTo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -39,7 +39,7 @@ abstract class ServerManager<T> {
private val extension: String = ".json"
protected suspend fun loadLocal(context: Context): T? {
val currentLanguage = context.getFirstSupportedLanguage()
val currentLanguage = context.getApplicationLanguage()
return loadFromFiles(context, currentLanguage)
?: loadFromAssets(context, currentLanguage)
......@@ -55,7 +55,7 @@ abstract class ServerManager<T> {
}
private suspend fun fetchLast(context: Context): Boolean {
val languageCode = context.getFirstSupportedLanguage()
val languageCode = context.getApplicationLanguage()
val filename = "$prefix$languageCode$extension"
val tmpFile = File(context.filesDir, "$filename.bck")
return try {
......@@ -135,7 +135,7 @@ abstract class ServerManager<T> {
}
fun clearLocal(context: Context) {
val filename = "$prefix${context.getFirstSupportedLanguage()}$extension"
val filename = "$prefix${context.getApplicationLanguage()}$extension"
File(context.filesDir, filename).delete()
val defaultFilename = "$prefix${UiConstants.DEFAULT_LANGUAGE}$extension"
File(context.filesDir, defaultFilename).delete()
......
......@@ -18,7 +18,7 @@ import com.lunabeestudio.robert.utils.Event
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.UiConstants
import com.lunabeestudio.stopcovid.coreui.extension.fixFormatter
import com.lunabeestudio.stopcovid.coreui.extension.getFirstSupportedLanguage
import com.lunabeestudio.stopcovid.coreui.extension.getApplicationLanguage
import java.lang.reflect.Type
typealias LocalizedStrings = HashMap<String, String>
......@@ -40,14 +40,14 @@ object StringsManager : ServerManager<LocalizedStrings>() {
private var prevLanguage: String? = null
suspend fun initialize(context: Context) {
prevLanguage = context.getFirstSupportedLanguage()
prevLanguage = context.getApplicationLanguage()
loadLocal(context)?.let {
strings = it
}
}
suspend fun onAppForeground(context: Context) {
val newLanguage = context.getFirstSupportedLanguage()
val newLanguage = context.getApplicationLanguage()
val languageHasChanged = prevLanguage != newLanguage
if (languageHasChanged) {
......@@ -69,6 +69,6 @@ object StringsManager : ServerManager<LocalizedStrings>() {
override val folderName: String = ConfigConstant.Labels.FOLDER
override val prefix: String = ConfigConstant.Labels.FILE_PREFIX
override val type: Type = object : TypeToken<LocalizedStrings>() {}.type
override val lastRefreshSharedPrefsKey: String = UiConstants.SharePrefs.LAST_STRINGS_REFRESH
override val lastRefreshSharedPrefsKey: String = UiConstants.SharedPrefs.LAST_STRINGS_REFRESH
override fun transform(input: String): String = input.fixFormatter()
}
\ No newline at end of file
......@@ -15,9 +15,7 @@ import android.os.Build
import com.lunabeestudio.domain.model.CacheConfig
import com.lunabeestudio.stopcovid.coreui.BuildConfig
import okhttp3.Cache
import okhttp3.CertificatePinner
import okhttp3.ConnectionSpec
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.TlsVersion
......@@ -29,7 +27,7 @@ import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
object OkHttpClient {
fun getDefaultOKHttpClient(context: Context, url: String, certificateSHA256: String, cacheConfig: CacheConfig?): OkHttpClient {
fun getDefaultOKHttpClient(context: Context, cacheConfig: CacheConfig?): OkHttpClient {
val requireTls12 = ConnectionSpec.Builder(ConnectionSpec.RESTRICTED_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
......@@ -38,11 +36,6 @@ object OkHttpClient {
connectionSpecs(listOf(requireTls12))
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
certificatePinner(
CertificatePinner.Builder()
.add(url.toHttpUrl().host, certificateSHA256)
.build()
)
val certificates: HandshakeCertificates = HandshakeCertificates.Builder()
.addTrustedCertificate(certificateFromString(context, "certigna_services"))
.build()
......
Subproject commit fc29367850453fac106114589a69ae9653eff61b
Subproject commit 76ba1bfd4f4b253088586c7a03c25f755bd1497e
......@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
......@@ -45,8 +45,8 @@ android {
applicationId "fr.gouv.android.stopcovid"
minSdkVersion 21
targetSdkVersion 30
versionCode 310
versionName "3.4.0"
versionCode 316
versionName "3.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......
......@@ -2,11 +2,11 @@
"config": [
{
"name": "lastUpdate",
"value": "9 Jul 2021"
"value": "16 Jul 2021"
},
{
"name": "version",
"value": 75
"value": 76
},
{
"name": "versionCalibrationBle",
......@@ -119,6 +119,10 @@
"name": "app.isolation.durationCovid",
"value": 864000
},
{
"name": "app.minFilesRefreshInterval",
"value": 5
},
{
"name": "app.displayRecordVenues",
"value": true
......@@ -256,7 +260,7 @@
"value":[
{
"code":"DEFAULT",
"value":14
"value":7
},
{
"code":"EU/1/20/1525",
......
......@@ -165,6 +165,17 @@ class StopCovid : Application(), LifecycleObserver, RobertApplication, Isolation
private var firstResume = false
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if (key == UiConstants.SharedPrefs.USER_LANGUAGE) {
appCoroutineScope.launch {
StringsManager.onAppForeground(this@StopCovid)
PrivacyManager.onAppForeground(this@StopCovid)
LinksManager.onAppForeground(this@StopCovid)
MoreKeyFiguresManager.onAppForeground(this@StopCovid)
}
}
}
init {
System.setProperty("kotlinx.coroutines.debug", if (BuildConfig.DEBUG) "on" else "off")
}
......@@ -248,6 +259,8 @@ class StopCovid : Application(), LifecycleObserver, RobertApplication, Isolation
startAppMaintenanceWorker(false)
AnalyticsManager.init(this)
sharedPrefs.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener)
sharedPrefs.lastVersionCode = BuildConfig.VERSION_CODE
}
......
......@@ -26,11 +26,11 @@ import com.google.gson.Gson
import com.lunabeestudio.stopcovid.R
import com.lunabeestudio.stopcovid.coreui.ConfigConstant
import com.lunabeestudio.stopcovid.coreui.extension.isNightMode
import com.lunabeestudio.stopcovid.coreui.extension.setTextOrHide
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
import com.lunabeestudio.stopcovid.extension.setTextOrHide
import com.lunabeestudio.stopcovid.manager.AppMaintenanceManager
import com.lunabeestudio.stopcovid.model.Info
import kotlinx.coroutines.launch
......
......@@ -39,19 +39,28 @@ import com.google.android.material.snackbar.Snackbar
import com.lunabeestudio.robert.extension.observeEventAndConsume
import com.lunabeestudio.stopcovid.Constants
import com.lunabeestudio.stopcovid.R
import com.lunabeestudio.stopcovid.coreui.UiConstants
import com.lunabeestudio.stopcovid.coreui.databinding.ItemDividerBinding
import com.lunabeestudio.stopcovid.coreui.extension.applyAndConsumeWindowInsetBottom
import com.lunabeestudio.stopcovid.coreui.extension.getApplicationLanguage
import com.lunabeestudio.stopcovid.coreui.extension.isNightMode
import com.lunabeestudio.stopcovid.coreui.extension.setTextOrHide
import com.lunabeestudio.stopcovid.coreui.extension.showSnackBar
import com.lunabeestudio.stopcovid.coreui.extension.userLanguage
import com.lunabeestudio.stopcovid.coreui.manager.LocalizedStrings
import com.lunabeestudio.stopcovid.coreui.manager.StringsManager
import com.lunabeestudio.stopcovid.databinding.ActivityMainBinding
import com.lunabeestudio.stopcovid.databinding.DialogUserLanguageBinding
import com.lunabeestudio.stopcovid.databinding.ItemSelectionBinding
import com.lunabeestudio.stopcovid.extension.alertRiskLevelChanged
import com.lunabeestudio.stopcovid.extension.flaggedCountry
import com.lunabeestudio.stopcovid.extension.isLaunchedFromHistory
import com.lunabeestudio.stopcovid.extension.robertManager
import com.lunabeestudio.stopcovid.extension.showAlertRiskLevelChanged
import com.lunabeestudio.stopcovid.manager.DeeplinkManager
import com.lunabeestudio.stopcovid.manager.RisksLevelManager
import kotlinx.coroutines.delay
import java.util.Locale
class MainActivity : BaseActivity() {
......@@ -93,6 +102,8 @@ class MainActivity : BaseActivity() {
if (intent?.isLaunchedFromHistory == false) {
handleIntent(intent)
}
showLanguageDialogIfNeeded()
}
override fun onResume() {
......@@ -296,6 +307,50 @@ class MainActivity : BaseActivity() {
return navController.navigateUp()
}
fun showLanguageDialogIfNeeded() {
if (sharedPrefs.userLanguage == null &&
UiConstants.SUPPORTED_LOCALES.map { it.language }.none { it == Locale.getDefault().language }) {
val userLanguageSelectionView = DialogUserLanguageBinding.inflate(layoutInflater)
var languageViewMap: Map<Locale, ItemSelectionBinding> = emptyMap()
languageViewMap = UiConstants.SUPPORTED_LOCALES.associateWith { locale ->
val selectionBinding = ItemSelectionBinding.inflate(layoutInflater, userLanguageSelectionView.root, false)
selectionBinding.titleTextView.setTextOrHide(locale.flaggedCountry)
selectionBinding.captionTextView.isVisible = false
selectionBinding.selectionImageView.isInvisible = locale.language != getApplicationLanguage()
selectionBinding.root.setOnClickListener {
sharedPrefs.userLanguage = locale.language
languageViewMap.forEach { (loopLocale, loopSelectionBinding) ->
loopSelectionBinding.selectionImageView.isInvisible = loopLocale.language != locale.language
}
}
selectionBinding
}
languageViewMap.values.forEachIndexed { idx, selectionBinding ->
val divider = ItemDividerBinding.inflate(layoutInflater, userLanguageSelectionView.root, false)
val dividerPos = idx * 2
userLanguageSelectionView.root.addView(divider.root, dividerPos)
userLanguageSelectionView.root.addView(selectionBinding.root, dividerPos + 1)
}
userLanguageSelectionView.userLanguageDialogFooter.text = strings["userLanguageController.footer"]
MaterialAlertDialogBuilder(this)
.setTitle(strings["userLanguageController.title"])
.setMessage(strings["userLanguageController.subtitle"])
.setView(userLanguageSelectionView.root)
.setPositiveButton(strings["userLanguageController.button.title"]) { _, _ ->
if (sharedPrefs.userLanguage == null) {
sharedPrefs.userLanguage = getApplicationLanguage()
}
}
.setCancelable(false)
.show()
}
}
companion object {
private const val CURFEW_CERTIFICATE_SHORTCUT_ID: String = "curfewCertificateShortcut"
private const val UNIVERSAL_QRCODE_SHORTCUT_ID: String = "universalQRCodeShortcut"
......
......@@ -5,15 +5,12 @@
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/10/29 - for the TOUS-ANTI-COVID project
* Created by Lunabee Studio / Date - 2021/20/7 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.stopcovid.extension
import android.widget.TextView
import androidx.core.view.isVisible
import java.util.Locale
fun TextView.setTextOrHide(text: CharSequence?) {
setText(text)
isVisible = !text.isNullOrBlank()
}
\ No newline at end of file
val Locale.flaggedCountry: String
get() = "${country.countryCodeToFlagEmoji} ${getDisplayLanguage(this).capitalizeWords()}"
\ No newline at end of file
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