MAJ terminée. Nous sommes passés en version 14.6.2 . Pour consulter les "releases notes" associées c'est ici :

https://about.gitlab.com/releases/2022/01/11/security-release-gitlab-14-6-2-released/
https://about.gitlab.com/releases/2022/01/04/gitlab-14-6-1-released/

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

Update to 2.0.1

parent c1645087
......@@ -15,6 +15,7 @@ import com.lunabeestudio.domain.model.DeviceParameterCorrection
import com.lunabeestudio.domain.model.HelloBuilder
import com.lunabeestudio.domain.model.LocalProximity
import com.lunabeestudio.robert.manager.LocalProximityFilter
import com.lunabeestudio.robert.model.AtRiskStatus
import com.lunabeestudio.robert.model.RobertResult
import com.lunabeestudio.robert.model.RobertResultData
import com.lunabeestudio.robert.utils.Event
......@@ -28,7 +29,7 @@ interface RobertManager {
val isAtRisk: Boolean?
val isAtRiskLiveData: LiveData<Event<Boolean?>>
val atRiskStatus: LiveData<Event<AtRiskStatus>>
val atRiskLastRefresh: Long?
......
......@@ -35,8 +35,10 @@ import com.lunabeestudio.robert.datasource.LocalLocalProximityDataSource
import com.lunabeestudio.robert.datasource.RemoteServiceDataSource
import com.lunabeestudio.robert.datasource.SharedCryptoDataSource
import com.lunabeestudio.robert.extension.safeEnumValueOf
import com.lunabeestudio.robert.extension.toAtRiskStatus
import com.lunabeestudio.robert.extension.use
import com.lunabeestudio.robert.manager.LocalProximityFilter
import com.lunabeestudio.robert.model.AtRiskStatus
import com.lunabeestudio.robert.model.NoEphemeralBluetoothIdentifierFound
import com.lunabeestudio.robert.model.NoEphemeralBluetoothIdentifierFoundForEpoch
import com.lunabeestudio.robert.model.NoKeyException
......@@ -101,8 +103,8 @@ class RobertManagerImpl(
override val isProximityActive: Boolean
get() = keystoreRepository.proximityActive ?: false
private val isAtRiskMutableLiveData: MutableLiveData<Event<Boolean?>> = MutableLiveData()
override val isAtRiskLiveData: LiveData<Event<Boolean?>> = isAtRiskMutableLiveData
private val atRiskMutableLiveData: MutableLiveData<Event<AtRiskStatus>> = MutableLiveData()
override val atRiskStatus: LiveData<Event<AtRiskStatus>> = atRiskMutableLiveData
override val isAtRisk: Boolean?
get() = keystoreRepository.lastRiskReceivedDate?.let {
......@@ -569,8 +571,10 @@ class RobertManagerImpl(
}
override fun refreshAtRisk() {
if (isAtRiskMutableLiveData.value?.peekContent() != isAtRisk) {
isAtRiskMutableLiveData.postValue(Event(isAtRisk))
isAtRisk.toAtRiskStatus().let { atRisk ->
if (atRiskMutableLiveData.value == null || atRiskMutableLiveData.value?.peekContent() != atRisk) {
atRiskMutableLiveData.postValue(Event(atRisk))
}
}
}
}
package com.lunabeestudio.robert.extension
import com.lunabeestudio.robert.model.AtRiskStatus
fun Boolean?.toAtRiskStatus(): AtRiskStatus = when (this) {
true -> AtRiskStatus.AT_RISK
false -> AtRiskStatus.NOT_AT_RISK
null -> AtRiskStatus.UNKNOWN
}
\ No newline at end of file
package com.lunabeestudio.robert.model
enum class AtRiskStatus {
UNKNOWN, NOT_AT_RISK, AT_RISK
}
\ No newline at end of file
......@@ -13,18 +13,18 @@ package com.lunabeestudio.robert.utils
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
class Event<out T>(private val content: T) {
private val hasBeenHandled: MutableSet<Int> = mutableSetOf()
private val handledIdSet: MutableSet<Int> = mutableSetOf()
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(id: Int): T? {
return if (hasBeenHandled.contains(id)) {
return if (handledIdSet.contains(id)) {
null
} else {
hasBeenHandled.add(id)
handledIdSet.add(id)
content
}
}
......
......@@ -44,8 +44,8 @@ android {
applicationId "fr.gouv.android.stopcovid"
minSdkVersion 21
targetSdkVersion 30
versionCode 70
versionName "2.0.0"
versionCode 74
versionName "2.0.1"
testInstrumentationRunner = 'com.lunabeestudio.stopcovid.TestRunner'
......
......@@ -25,9 +25,7 @@ class ImageBackgroundCardItem : AbstractBindingItem<ItemImageBackgroundCardBindi
var title: String? = null
var subtitle: String? = null
var onClickListener: View.OnClickListener? = null
@DrawableRes
var backgroundDrawable: Int? = null
var isAtRisk: Boolean = false
@DrawableRes
var iconRes: Int? = null
......@@ -44,7 +42,11 @@ class ImageBackgroundCardItem : AbstractBindingItem<ItemImageBackgroundCardBindi
binding.titleTextView.setTextOrHide(title)
binding.subtitleTextView.setTextOrHide(subtitle)
binding.imageView.setImageResourceOrHide(iconRes)
backgroundDrawable?.let { binding.constraintLayout.setBackgroundResource(it) }
if (isAtRisk) {
binding.constraintLayout.setBackgroundResource(R.drawable.bg_risk)
} else {
binding.constraintLayout.setBackgroundResource(R.drawable.bg_no_risk)
}
binding.root.setOnClickListener(onClickListener)
}
......
......@@ -20,20 +20,20 @@ import com.lunabeestudio.stopcovid.databinding.ItemInfoCenterDetailCardBinding
import com.lunabeestudio.stopcovid.extension.openInExternalBrowser
import com.lunabeestudio.stopcovid.extension.setTextOrHide
import com.lunabeestudio.stopcovid.model.InfoCenterTag
import com.mikepenz.fastadapter.GenericItem
import com.mikepenz.fastadapter.adapters.FastItemAdapter
import com.mikepenz.fastadapter.binding.AbstractBindingItem
import com.mikepenz.fastadapter.binding.BindingViewHolder
class InfoCenterDetailCardItem : AbstractBindingItem<ItemInfoCenterDetailCardBinding>() {
var header: String? = null
var tags: List<InfoCenterTag> = emptyList()
var strings: Map<String, String> = emptyMap()
var title: String? = null
var subtitle: String? = null
var body: String? = null
var link: String? = null
var url: String? = null
private val fastItemAdapter: FastItemAdapter<GenericItem> = FastItemAdapter()
var tagRecyclerViewPool: RecyclerView.RecycledViewPool? = null
override val type: Int = R.id.item_info_center_detail_card
......@@ -41,11 +41,26 @@ class InfoCenterDetailCardItem : AbstractBindingItem<ItemInfoCenterDetailCardBin
return ItemInfoCenterDetailCardBinding.inflate(inflater, parent, false)
}
override fun bindView(holder: BindingViewHolder<ItemInfoCenterDetailCardBinding>, payloads: List<Any>) {
super.bindView(holder, payloads)
val tagItems = tags
.filter { strings[it.labelKey]?.isNotEmpty() ?: false }
.map { (id, labelKey, colorCode) ->
tagItem {
text = strings[labelKey]
color = colorCode
identifier = id.hashCode().toLong()
}
}
(holder as InfoCenterDetailCardItemViewHolder).tagAdapter.setNewList(tagItems)
}
override fun bindView(binding: ItemInfoCenterDetailCardBinding, payloads: List<Any>) {
super.bindView(binding, payloads)
binding.headerTextView.setTextOrHide(header)
binding.titleTextView.setTextOrHide(title)
binding.subtitleTextView.setTextOrHide(subtitle)
binding.bodyTextView.setTextOrHide(body)
binding.includeLink.textView.setTextOrHide(link)
binding.includeLink.leftIconImageView.isVisible = false
binding.space.isVisible = link == null || url == null
......@@ -53,26 +68,37 @@ class InfoCenterDetailCardItem : AbstractBindingItem<ItemInfoCenterDetailCardBin
binding.includeLink.constraintLayout.setOnClickListener {
url?.openInExternalBrowser(it.context)
}
binding.recyclerView.layoutManager = LinearLayoutManager(
binding.root.context,
binding.tagRecyclerView.isVisible = tags.isNotEmpty()
}
override fun getViewHolder(viewBinding: ItemInfoCenterDetailCardBinding): BindingViewHolder<ItemInfoCenterDetailCardBinding> {
val viewHolder = InfoCenterDetailCardItemViewHolder(viewBinding)
viewBinding.tagRecyclerView.layoutManager = LinearLayoutManager(
viewBinding.root.context,
RecyclerView.HORIZONTAL,
false
)
binding.recyclerView.adapter = fastItemAdapter
fastItemAdapter.set(
tags.map { tag ->
tagItem {
text = strings[tag.labelKey]
color = tag.colorCode
identifier = tag.id.hashCode().toLong()
}
}
)
binding.recyclerView.isVisible = tags.isNotEmpty()
viewBinding.tagRecyclerView.setHasFixedSize(true)
viewBinding.tagRecyclerView.adapter = viewHolder.tagAdapter
tagRecyclerViewPool?.let {
viewBinding.tagRecyclerView.setRecycledViewPool(it)
}
return viewHolder
}
}
class InfoCenterDetailCardItemViewHolder(binding: ItemInfoCenterDetailCardBinding)
: BindingViewHolder<ItemInfoCenterDetailCardBinding>(binding) {
val tagAdapter: FastItemAdapter<TagItem> = FastItemAdapter()
init {
tagAdapter.setHasStableIds(true)
}
}
fun infoCenterDetailCardItem(block: (InfoCenterDetailCardItem.() -> Unit)): InfoCenterDetailCardItem = InfoCenterDetailCardItem().apply(
block
)
\ No newline at end of file
)
......@@ -31,7 +31,7 @@ import java.util.Locale
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds
class HealthFragment : TimeMainFragment(), PopupMenu.OnMenuItemClickListener {
class HealthFragment : TimeMainFragment() {
private val robertManager by lazy {
requireContext().robertManager()
......@@ -58,7 +58,7 @@ class HealthFragment : TimeMainFragment(), PopupMenu.OnMenuItemClickListener {
viewModel.covidException.observe(viewLifecycleOwner) { covidException ->
showErrorSnackBar(covidException.getString(strings))
}
robertManager.isAtRiskLiveData.observe(viewLifecycleOwner, EventObserver {
robertManager.atRiskStatus.observe(viewLifecycleOwner, EventObserver(this.javaClass.name.hashCode()) {
refreshScreen()
})
}
......@@ -73,7 +73,8 @@ class HealthFragment : TimeMainFragment(), PopupMenu.OnMenuItemClickListener {
private fun showMenu(v: View) {
PopupMenu(requireContext(), v).apply {
setOnMenuItemClickListener(this@HealthFragment)
setOnMenuItemClickListener(::onMenuItemClick)
inflate(R.menu.notification_menu)
menu.findItem(R.id.notification_menu_delete).title = strings["sickController.state.deleteNotification"]
......@@ -83,7 +84,7 @@ class HealthFragment : TimeMainFragment(), PopupMenu.OnMenuItemClickListener {
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.notification_menu_delete -> {
MaterialAlertDialogBuilder(requireContext())
......
......@@ -5,6 +5,7 @@ import android.view.View
import androidx.core.content.edit
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.lunabeestudio.robert.utils.EventObserver
import com.lunabeestudio.stopcovid.Constants
import com.lunabeestudio.stopcovid.R
......@@ -20,6 +21,8 @@ import kotlin.time.seconds
class InfoCenterFragment : TimeMainFragment() {
private val tagRecyclerPool = RecyclerView.RecycledViewPool()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
......@@ -58,30 +61,30 @@ class InfoCenterFragment : TimeMainFragment() {
val items = ArrayList<GenericItem>()
val infoCenterStrings = InfoCenterManager.strings.value?.peekContent()
val infos = InfoCenterManager.infos.value?.peekContent()
val tags = InfoCenterManager.tags.value?.peekContent()
val infos = InfoCenterManager.infos.value?.peekContent() ?: emptyList()
val tags = InfoCenterManager.tags.value?.peekContent() ?: emptyList()
if (infoCenterStrings != null && infos != null && tags != null) {
if (infoCenterStrings != null) {
infos.forEach { info ->
info.tagIds?.mapNotNull { tagId ->
tags.firstOrNull { tag ->
tag.id == tagId
}
}?.filter {
infoCenterStrings[it.labelKey] != null
val filteredTags = info.tagIds?.map { tagIds ->
tags.first { it.id == tagIds }
}
items += infoCenterDetailCardItem {
header = info.timestamp.seconds.getRelativeDateTimeString(requireContext())
title = infoCenterStrings[info.titleKey]
subtitle = infoCenterStrings[info.descriptionKey]
body = infoCenterStrings[info.descriptionKey]
link = infoCenterStrings[info.buttonLabelKey]
this.tags = tags ?: emptyList()
this.tags = filteredTags ?: emptyList()
strings = infoCenterStrings
url = infoCenterStrings[info.urlKey]
identifier = info.timestamp
tagRecyclerViewPool = this@InfoCenterFragment.tagRecyclerPool
identifier = info.titleKey.hashCode().toLong()
}
items += spaceItem {
spaceRes = R.dimen.spacing_large
identifier = items.size.toLong()
}
}
}
......
......@@ -34,7 +34,7 @@ class InformationFragment : MainFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
robertManager.isAtRiskLiveData.observe(viewLifecycleOwner, EventObserver {
robertManager.atRiskStatus.observe(viewLifecycleOwner, EventObserver(this.javaClass.name.hashCode()) {
refreshScreen()
})
}
......
......@@ -42,6 +42,7 @@ import androidx.navigation.navOptions
import androidx.preference.PreferenceManager
import com.airbnb.lottie.utils.Utils
import com.lunabeestudio.robert.RobertApplication
import com.lunabeestudio.robert.model.AtRiskStatus
import com.lunabeestudio.robert.model.RobertException
import com.lunabeestudio.robert.utils.EventObserver
import com.lunabeestudio.stopcovid.Constants
......@@ -108,7 +109,7 @@ class ProximityFragment : TimeMainFragment() {
private lateinit var proximityButtonItem: ProximityButtonItem
private lateinit var infoCenterCardItem: InfoCenterCardItem
private lateinit var numbersCardItem: NumbersCardItem
private lateinit var healthItem: ImageBackgroundCardItem
private var healthItem: ImageBackgroundCardItem? = null
private var shouldRefresh: Boolean = false
private var isProximityOn: Boolean = false
private val interpolator = DecelerateInterpolator()
......@@ -204,8 +205,16 @@ class ProximityFragment : TimeMainFragment() {
viewModel.activateProximitySuccess.observe(viewLifecycleOwner) {
refreshItems()
}
robertManager.isAtRiskLiveData.observe(viewLifecycleOwner, EventObserver {
refreshScreen()
robertManager.atRiskStatus.observe(viewLifecycleOwner, EventObserver(this.javaClass.name.hashCode()) { isAtRisk ->
when {
isAtRisk == AtRiskStatus.UNKNOWN -> {
healthItem = null
refreshScreen()
}
healthItem == null -> refreshScreen()
isAtRisk == AtRiskStatus.AT_RISK -> refreshHealthItemAsync(true)
isAtRisk == AtRiskStatus.NOT_AT_RISK -> refreshHealthItemAsync(false)
}
})
InfoCenterManager.infos.observe(viewLifecycleOwner, EventObserver(this.javaClass.name.hashCode()) {
refreshScreen()
......@@ -241,7 +250,7 @@ class ProximityFragment : TimeMainFragment() {
addTopImageItems(items)
addActivateButtonItems(items)
addHealthItems(items)
healthItem?.let { items += it } ?: addHealthItems(items)
addNewsItems(items)
addDeclareItems(items)
addSharingItems(items)
......@@ -267,14 +276,13 @@ class ProximityFragment : TimeMainFragment() {
}
private fun addActivateButtonItems(items: ArrayList<GenericItem>) {
val proximityActive = robertManager.isProximityActive
proximityButtonItem = proximityButtonItem {
mainText = strings["home.mainButton.activate"]
lightText = strings["home.mainButton.deactivate"]
onClickListener = View.OnClickListener {
if (SystemClock.elapsedRealtime() > proximityClickThreshold) {
proximityClickThreshold = SystemClock.elapsedRealtime() + PROXIMITY_BUTTON_DELAY
if (proximityActive) {
if (robertManager.isProximityActive) {
deactivateProximity()
refreshItems()
} else {
......@@ -293,7 +301,7 @@ class ProximityFragment : TimeMainFragment() {
onClickListener = View.OnClickListener {
if (SystemClock.elapsedRealtime() > proximityClickThreshold) {
proximityClickThreshold = SystemClock.elapsedRealtime() + PROXIMITY_BUTTON_DELAY
if (proximityActive) {
if (robertManager.isProximityActive) {
deactivateProximity()
refreshItems()
} else {
......@@ -312,26 +320,24 @@ class ProximityFragment : TimeMainFragment() {
}
private fun addHealthItems(items: ArrayList<GenericItem>) {
val atRisk = robertManager.isAtRisk
healthItem = imageBackgroundCardItem {
iconRes = R.drawable.health_card
backgroundDrawable = if (atRisk == true) {
R.drawable.bg_risk
} else {
R.drawable.bg_no_risk
}
onClickListener = View.OnClickListener {
findNavController().navigate(ProximityFragmentDirections.actionProximityFragmentToHealthFragment())
robertManager.isAtRisk?.let { atRisk ->
healthItem = imageBackgroundCardItem {
iconRes = R.drawable.health_card
isAtRisk = atRisk
onClickListener = View.OnClickListener {
findNavController().navigate(ProximityFragmentDirections.actionProximityFragmentToHealthFragment())
}
identifier = items.count().toLong()
}
identifier = items.count().toLong()
}
if (atRisk != null) {
items += healthItem
}
items += spaceItem {
spaceRes = R.dimen.spacing_large
identifier = items.count().toLong()
refreshHealthItemAsync(atRisk)
healthItem?.let { items += it }
items += spaceItem {
spaceRes = R.dimen.spacing_large
identifier = items.count().toLong()
}
}
}
......@@ -520,8 +526,6 @@ class ProximityFragment : TimeMainFragment() {
R.drawable.status_inactive
}
refreshHealthItem()
val infoCenterStrings = InfoCenterManager.strings.value?.peekContent() ?: emptyMap()
InfoCenterManager.infos.value?.peekContent()?.firstOrNull()?.let { info ->
......@@ -583,20 +587,29 @@ class ProximityFragment : TimeMainFragment() {
}
@OptIn(ExperimentalTime::class)
private fun refreshHealthItem() {
private fun refreshHealthItemAsync(isAtRisk: Boolean) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
healthItem.apply {
healthItem?.apply {
header = stringsFormat(
"myHealthController.notification.update",
robertManager.atRiskLastRefresh?.milliseconds?.getRelativeDateTimeString(requireContext())
)
if (robertManager.isAtRisk == true) {
if (isAtRisk) {
title = strings["home.healthSection.contact.cellTitle"]
subtitle = strings["home.healthSection.contact.cellSubtitle"]
} else {
title = strings["home.healthSection.noContact.cellTitle"]
subtitle = strings["home.healthSection.noContact.cellSubtitle"]
}
this.isAtRisk = isAtRisk
}
if (binding?.recyclerView?.isComputingLayout == false) {
withContext(Dispatchers.Main) {
binding?.recyclerView?.adapter?.notifyDataSetChanged()
}
}
}
}
......@@ -604,7 +617,7 @@ class ProximityFragment : TimeMainFragment() {
@OptIn(ExperimentalTime::class)
override fun timeRefresh() {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
healthItem.apply {
healthItem?.apply {
header = stringsFormat(
"myHealthController.notification.update",
robertManager.atRiskLastRefresh?.milliseconds?.getRelativeDateTimeString(requireContext())
......@@ -615,6 +628,7 @@ class ProximityFragment : TimeMainFragment() {
subheader = info.timestamp.seconds.getRelativeDateTimeString(requireContext())
}
}
if (binding?.recyclerView?.isComputingLayout == false) {
withContext(Dispatchers.Main) {
binding?.recyclerView?.adapter?.notifyDataSetChanged()
......
......@@ -9,7 +9,6 @@
-->
<androidx.constraintlayout.widget.ConstraintLayout
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="wrap_content"
......
......@@ -41,7 +41,7 @@
tools:text="@tools:sample/lorem[14]" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:id="@+id/tagRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
......@@ -49,7 +49,7 @@
android:paddingEnd="@dimen/spacing_large" />
<TextView
android:id="@+id/subtitleTextView"
android:id="@+id/bodyTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/spacing_large"
......
......@@ -9,7 +9,6 @@
-->
<androidx.constraintlayout.widget.ConstraintLayout
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="wrap_content"
......
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