From beaa8c93f0defb51f6d5145659785a78ace858d1 Mon Sep 17 00:00:00 2001 From: Alex Paulescu <alex.paulescu@gmail.com> Date: Wed, 10 Mar 2021 16:29:56 +0200 Subject: [PATCH] Replace active tracing days with days since installation (EXPOSUREAPP-5170) (#2522) * Added new strings. * Implemented first install time logic. * Adapted screens to first install time. * Fixed layout issues in tracing settings. * Instrumentation test fix. * Addressed PR comments. * Removed mstodays transformation. * Added missing daysSinceInstallation value. * Removed unnecessary constraint layouts. * Fixed constraints. * Unified text styles to subtitles. * Over 14 days text displayed from 14 days onwards. * Minor UI adjustments. * Risk card not showing days since install when lowe risk encountered. Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com> Co-authored-by: Juraj Kusnier <jurajkusnier@users.noreply.github.com> --- .../coronawarnapp/ui/main/home/HomeData.kt | 6 +- .../coronawarnapp/ui/tracing/TracingData.kt | 8 + .../installTime/InstallTimeProvider.kt | 19 ++ .../tracing/states/TracingState.kt | 11 +- .../tracing/states/TracingStateProvider.kt | 8 +- .../ui/details/TracingDetailsItemProvider.kt | 3 + .../items/periodlogged/PeriodLoggedBox.kt | 16 +- .../SettingsTracingFragmentViewModel.kt | 9 +- .../coronawarnapp/ui/view/CircleProgress.kt | 168 ------------------ .../src/main/res/drawable/ic_download.xml | 13 ++ .../res/layout/fragment_settings_tracing.xml | 56 ++++-- .../layout/tracing_content_increased_view.xml | 10 +- .../res/layout/tracing_content_low_view.xml | 24 ++- ...tracing_details_item_periodlogged_view.xml | 79 ++++---- .../tracing/states/LowRiskTest.kt | 1 + .../details/TracingDetailsItemProviderTest.kt | 24 ++- 16 files changed, 211 insertions(+), 244 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_download.xml diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt index 4839c083c..b3ee5d135 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt @@ -42,7 +42,8 @@ object HomeData { lastExposureDetectionTime = Instant.now(), lastEncounterAt = null, allowManualUpdate = false, - daysWithEncounters = 0 + daysWithEncounters = 0, + daysSinceInstallation = 4, ), onCardClick = {}, onUpdateClick = {} @@ -55,7 +56,8 @@ object HomeData { lastExposureDetectionTime = Instant.now(), lastEncounterAt = Instant.now(), allowManualUpdate = false, - daysWithEncounters = 1 + daysWithEncounters = 1, + daysSinceInstallation = 4 ), onCardClick = {}, onUpdateClick = {} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt index b5dcdc27b..a076477cb 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt @@ -41,6 +41,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_INACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -61,6 +62,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 0, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -70,6 +72,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -90,6 +93,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 1, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -99,6 +103,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -119,6 +124,7 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 2, + daysSinceInstallation = 4, lastEncounterAt = Instant.now() ) ), @@ -128,6 +134,7 @@ object TracingData { ), PeriodLoggedBox.Item( + daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) @@ -153,6 +160,7 @@ object TracingData { ), BehaviorIncreasedRiskBox.Item, PeriodLoggedBox.Item( + daysSinceInstallation = 5, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), DetailsIncreasedRiskBox.Item( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt new file mode 100644 index 000000000..9aa763874 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/installTime/InstallTimeProvider.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.installTime + +import android.content.Context +import de.rki.coronawarnapp.util.TimeAndDateExtensions.roundUpMsToDays +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class InstallTimeProvider @Inject constructor( + @AppContext private val context: Context +) { + private val installTime: Long = context + .packageManager + .getPackageInfo(context.packageName, 0) + .firstInstallTime + + val daysSinceInstallation: Long get() = (System.currentTimeMillis() - installTime).roundUpMsToDays() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt index 35b60f17d..0c5b12843 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt @@ -84,8 +84,6 @@ data class IncreasedRisk( lastEncounterAt.toLocalDate().toString(DateTimeFormat.mediumDate()) ) } - - fun getProgressColorHighRisk(context: Context) = context.getColorCompat(R.color.colorStableLight) } // tracing_content_low_view @@ -95,7 +93,8 @@ data class LowRisk( val lastExposureDetectionTime: Instant?, val lastEncounterAt: Instant?, val allowManualUpdate: Boolean, - val daysWithEncounters: Int + val daysWithEncounters: Int, + val daysSinceInstallation: Long ) : TracingState() { val showUpdateButton: Boolean = allowManualUpdate && !isInDetailsMode @@ -131,6 +130,12 @@ data class LowRisk( ) } + fun getDaysSinceInstall(context: Context): String = + context.getString(R.string.risk_card_body_days_since_installation) + .format(daysSinceInstallation) + + fun appInstalledForOverTwoWeeks(): Boolean = daysSinceInstallation < 14 && lastEncounterAt == null + fun getRiskContactLast(context: Context): String? { if (lastEncounterAt == null) return null // caution! lastEncounterAt is null after migration from 1.7.x -> 1.8.x diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt index 2e7017757..c5168a647 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.tracing.states import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.latestSubmission import de.rki.coronawarnapp.risk.RiskState @@ -25,9 +26,9 @@ class TracingStateProvider @AssistedInject constructor( backgroundModeStatus: BackgroundModeStatus, tracingRepository: TracingRepository, riskLevelStorage: RiskLevelStorage, - exposureDetectionTracker: ExposureDetectionTracker + exposureDetectionTracker: ExposureDetectionTracker, + installTimeProvider: InstallTimeProvider ) { - val state: Flow<TracingState> = combine( tracingStatus.generalStatus.onEach { Timber.v("tracingStatus: $it") @@ -72,7 +73,8 @@ class TracingStateProvider @AssistedInject constructor( lastExposureDetectionTime = latestSubmission?.startedAt, lastEncounterAt = latestCalc.lastRiskEncounterAt, daysWithEncounters = latestCalc.daysWithEncounters, - allowManualUpdate = !isBackgroundJobEnabled + allowManualUpdate = !isBackgroundJobEnabled, + daysSinceInstallation = installTimeProvider.daysSinceInstallation ) latestCalc.riskState == RiskState.INCREASED_RISK -> IncreasedRisk( isInDetailsMode = isDetailsMode, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt index 68ffdbac5..41de9b928 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.tracing.ui.details import dagger.Reusable import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults @@ -29,6 +30,7 @@ import javax.inject.Inject class TracingDetailsItemProvider @Inject constructor( tracingStatus: GeneralTracingStatus, riskLevelStorage: RiskLevelStorage, + installTimeProvider: InstallTimeProvider, surveys: Surveys ) { @@ -68,6 +70,7 @@ class TracingDetailsItemProvider @Inject constructor( if (latestCalc.riskState != RiskState.CALCULATION_FAILED && status != Status.TRACING_INACTIVE) { PeriodLoggedBox.Item( + daysSinceInstallation = installTimeProvider.daysSinceInstallation, tracingStatus = status ).also { add(it) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt index 985a26953..beb754001 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/periodlogged/PeriodLoggedBox.kt @@ -8,7 +8,6 @@ import de.rki.coronawarnapp.databinding.TracingDetailsItemPeriodloggedViewBindin import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsAdapter import de.rki.coronawarnapp.tracing.ui.details.items.DetailsItem -import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat class PeriodLoggedBox( parent: ViewGroup, @@ -34,15 +33,18 @@ class PeriodLoggedBox( } data class Item( + val daysSinceInstallation: Long, val tracingStatus: GeneralTracingStatus.Status ) : DetailsItem { - fun getProgressColor(context: Context) = when (tracingStatus) { - GeneralTracingStatus.Status.TRACING_INACTIVE, - GeneralTracingStatus.Status.BLUETOOTH_DISABLED, - GeneralTracingStatus.Status.LOCATION_DISABLED -> R.color.colorTextPrimary2 - GeneralTracingStatus.Status.TRACING_ACTIVE -> R.color.colorAccentTintIcon - }.let { context.getColorCompat(it) } + fun getInstallTimePeriodLogged(context: Context): String = + if (daysSinceInstallation < 14L) { + context.getString( + R.string.risk_details_information_body_period_logged_assessment_under_14_days + ).format(daysSinceInstallation) + } else context.getString( + R.string.risk_details_information_body_period_logged_assessment_over_14_days + ) override val stableId: Long get() = Item::class.java.name.hashCode().toLong() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt index 37a9cdd1e..76af2958e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/settings/SettingsTracingFragmentViewModel.kt @@ -10,6 +10,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.TracingPermissionHelper import de.rki.coronawarnapp.tracing.GeneralTracingStatus @@ -31,12 +32,18 @@ import timber.log.Timber class SettingsTracingFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, tracingStatus: GeneralTracingStatus, + installTimeProvider: InstallTimeProvider, private val backgroundStatus: BackgroundModeStatus, tracingPermissionHelperFactory: TracingPermissionHelper.Factory ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val loggingPeriod: LiveData<PeriodLoggedBox.Item> = - tracingStatus.generalStatus.map { PeriodLoggedBox.Item(it) } + tracingStatus.generalStatus.map { + PeriodLoggedBox.Item( + daysSinceInstallation = installTimeProvider.daysSinceInstallation, + tracingStatus = it + ) + } .onEach { Timber.v("logginPeriod onEach") } .asLiveData(dispatcherProvider.Main) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt deleted file mode 100644 index 455b50499..000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt +++ /dev/null @@ -1,168 +0,0 @@ -package de.rki.coronawarnapp.ui.view - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.RectF -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import androidx.core.content.ContextCompat -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.ViewCircleProgressBinding -import de.rki.coronawarnapp.risk.TimeVariables - -/** - * Used on the tracing details fragment without text and also on the risk card with the progress - * number in the circle. - * - * @param context - * @param attrs - * @param defStyleAttr - */ -class CircleProgress @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - companion object { - private const val START_ANGLE = 270f - private const val FULL_CIRCLE = 360f - private const val DEFAULT_WIDTH = 10f - private val DEFAULT_MAX_PROGRESS = TimeVariables.getDefaultRetentionPeriodInDays().toFloat() - } - - private val circlePaint: Paint - private var progressPaint: Paint - private val rect = RectF() - private var binding: ViewCircleProgressBinding - private var centerX: Float = 0f - private var centerY: Float = 0f - private var radius: Float = 0f - private var progressWidth: Float = 0f - private var disableText: Boolean = false - - /** - * Setter for progress. Text and icon depend on the progress value. - * The visibility is also influenced by the disableText attribute. - */ - var progress: Float = 0F - set(value) { - field = value - val body = binding.circleProgressBody - val icon = binding.circleProgressIcon - // text visibility - if (disableText || value == DEFAULT_MAX_PROGRESS) { - body.visibility = View.GONE - } else { - body.visibility = View.VISIBLE - body.text = context.getString(R.string.risk_details_information_active_tracing_days_circle_progress) - .format(value.toInt()) - } - // icon visibility - if (value == DEFAULT_MAX_PROGRESS) { - icon.visibility = View.VISIBLE - } else { - icon.visibility = View.GONE - } - invalidate() - } - - /** - * Setter for the progress circle color. - * The progress bar also needs to be repainted when the - * color changes (ex: when the risk calculation gets turned on/off) - */ - var progressColor: Int = Color.TRANSPARENT - set(value) { - field = value - binding.circleProgressIcon.setColorFilter(value, android.graphics.PorterDuff.Mode.SRC_IN) - progressPaint = paintProgressCircle() - invalidate() - } - - /** - * Initialise the view with the following attributes or some default values: - * - circleColor - * - textColor - * - disableText - * - progressWidth - */ - init { - setWillNotDraw(false) - binding = ViewCircleProgressBinding.inflate(LayoutInflater.from(context), this) - val styleAttrs = context.obtainStyledAttributes(attrs, R.styleable.CircleProgress) - val circleColor = styleAttrs.getColor( - R.styleable.CircleProgress_circleColor, - ContextCompat.getColor(context, R.color.colorSurface2) - ) - // attribute progressColor; default = colorAccentTintIcon - val progressColor = styleAttrs.getColor( - R.styleable.CircleProgress_progressColor, - ContextCompat.getColor(context, R.color.colorAccentTintIcon) - ) - // attribute textColor; default = colorTextPrimary2 - val textColor = styleAttrs.getColor( - R.styleable.CircleProgress_textColor, - ContextCompat.getColor(context, R.color.colorTextPrimary2) - ) - // attribute disableText; default = true - disableText = styleAttrs.getBoolean(R.styleable.CircleProgress_disableText, false) - // attribute progressWidth; default = DEFAULT_WIDTH - progressWidth = styleAttrs.getFloat(R.styleable.CircleProgress_circleWidth, DEFAULT_WIDTH) - // attribute progress; default = 0 - progress = styleAttrs.getFloat(R.styleable.CircleProgress_progress, 0F) - // set textColor - val body = binding.circleProgressBody - body.setTextColor(textColor) - // set icon color - val icon = binding.circleProgressIcon - icon.setColorFilter(progressColor, android.graphics.PorterDuff.Mode.SRC_IN) - // circlePaint based on the attributes and default value - circlePaint = Paint().apply { - color = circleColor - style = Paint.Style.STROKE - strokeWidth = progressWidth - isAntiAlias = true - } - // progressPaint based on the attributes and default value - progressPaint = paintProgressCircle() - styleAttrs.recycle() - } - - private fun paintProgressCircle() = - Paint().apply { - color = progressColor - style = Paint.Style.STROKE - strokeWidth = progressWidth - isAntiAlias = true - strokeCap = Paint.Cap.ROUND - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - centerX = w.toFloat().div(2) - centerY = h.toFloat().div(2) - radius = w.toFloat().div(2).minus(progressWidth) - rect.set( - centerX.minus(radius), - centerY.minus(radius), - centerX.plus(radius), - centerY.plus(radius) - ) - super.onSizeChanged(w, h, oldw, oldh) - } - - override fun onDraw(canvas: Canvas?) { - super.onDraw(canvas) - canvas?.drawCircle(centerX, centerY, radius, circlePaint) - canvas?.drawArc( - rect, - START_ANGLE, - FULL_CIRCLE.times(progress).div(DEFAULT_MAX_PROGRESS), - false, - progressPaint - ) - } -} diff --git a/Corona-Warn-App/src/main/res/drawable/ic_download.xml b/Corona-Warn-App/src/main/res/drawable/ic_download.xml new file mode 100644 index 000000000..17573ee7c --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_download.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="40dp" + android:height="42dp" + android:viewportWidth="37" + android:viewportHeight="39"> + <group + android:translateX="8.5" + android:translateY="8.5"> + <path + android:fillColor="#ffffff" + android:pathData="M10,2.0132C14.41,2.0132 18,5.6032 18,10.0132C18,14.4232 14.41,18.0132 10,18.0132C5.59,18.0132 2,14.4232 2,10.0132C2,5.6032 5.59,2.0132 10,2.0132ZM10,0.0132C4.48,0.0132 0,4.4932 0,10.0132C0,15.5332 4.48,20.0132 10,20.0132C15.52,20.0132 20,15.5332 20,10.0132C20,4.4932 15.52,0.0132 10,0.0132ZM11,10.0132V6.0132H9V10.0132H6L10,14.0132L14,10.0132H11Z" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml index 617cb80e2..de95af036 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml @@ -169,8 +169,8 @@ app:buttonText="@{@string/settings_tracing_status_location_button}" app:headline="@{@string/settings_tracing_status_location_headline}" app:icon="@{@drawable/ic_location}" - app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" - app:layout_constraintStart_toStartOf="@+id/guideline_card_start" + app:layout_constraintEnd_toStartOf="@id/guideline_card_end" + app:layout_constraintStart_toStartOf="@id/guideline_card_start" app:layout_constraintTop_toTopOf="parent" /> <include @@ -183,22 +183,58 @@ app:buttonText="@{@string/settings_tracing_status_bluetooth_button}" app:headline="@{@string/settings_tracing_status_bluetooth_headline}" app:icon="@{@drawable/ic_settings_tracing_bluetooth}" - app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" - app:layout_constraintStart_toStartOf="@+id/guideline_card_start" + app:layout_constraintEnd_toStartOf="@id/guideline_card_end" + app:layout_constraintStart_toStartOf="@id/guideline_card_start" app:layout_constraintTop_toTopOf="parent" /> <TextView - android:id="@+id/risk_details_period_logged_body_notice" - style="@style/subtitleMedium" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:id="@+id/risk_details_period_logged_headline" + style="@style/headline5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:focusable="false" + android:text="@string/risk_details_headline_period_logged" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/settings_tracing_status_bluetooth" /> + + <TextView + android:id="@+id/risk_details_period_logged_subtitle" + style="@style/subtitle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_tiny" + android:focusable="false" + android:text="@string/risk_details_subtitle_period_logged" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> + + <TextView + android:id="@id/risk_details_period_logged_body_notice" + style="@style/subtitle" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" android:focusable="true" android:text="@string/risk_details_information_body_period_logged" - app:layout_constraintEnd_toStartOf="@+id/guideline_end" - app:layout_constraintStart_toStartOf="@+id/guideline_start" - app:layout_constraintTop_toBottomOf="@+id/settings_tracing_status_bluetooth" /> + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> + + <TextView + android:id="@+id/risk_details_period_logged_days" + style="@style/subtitle" + gone="@{!settingsTracingState.isTracingStatusTextVisible()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" + android:text="@{loggedPeriod.getInstallTimePeriodLogged(context)}" + app:layout_constraintEnd_toStartOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_body_notice" /> <include layout="@layout/merge_guidelines_card" /> diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml index 2d2c4b51f..12af62996 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml @@ -24,7 +24,7 @@ android:accessibilityHeading="true" android:text="@string/risk_card_increased_risk_headline" android:textColor="@color/colorTextPrimary1InvertedStable" - app:layout_constraintEnd_toStartOf="@+id/details_icon" + app:layout_constraintEnd_toStartOf="@id/details_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_goneMarginEnd="0dp" /> @@ -52,7 +52,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/headline" + app:layout_constraintTop_toBottomOf="@id/headline" tools:text="@plurals/risk_card_high_risk_encounter_days_body" /> <de.rki.coronawarnapp.ui.view.TracingCardInfoRow @@ -66,7 +66,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact" + app:layout_constraintTop_toBottomOf="@id/row_contact" tools:text="@string/risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day" /> <androidx.constraintlayout.widget.ConstraintLayout @@ -90,7 +90,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_tracing_days" + app:layout_constraintTop_toBottomOf="@id/row_contact_last" tools:text="@string/risk_card_body_not_yet_fetched" /> <Button @@ -103,7 +103,7 @@ android:text="@string/risk_card_button_update" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_time_fetched" /> + app:layout_constraintTop_toBottomOf="@id/row_time_fetched" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml index 048ee99fe..1fbb229ef 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml @@ -24,7 +24,7 @@ android:accessibilityHeading="true" android:text="@string/risk_card_low_risk_headline" android:textColor="@color/colorTextPrimary1InvertedStable" - app:layout_constraintEnd_toStartOf="@+id/details_icon" + app:layout_constraintEnd_toStartOf="@id/details_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_goneMarginEnd="0dp" @@ -55,7 +55,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/headline" + app:layout_constraintTop_toBottomOf="@id/headline" tools:text="@plurals/risk_card_low_risk_encounter_days_body" /> <de.rki.coronawarnapp.ui.view.TracingCardInfoRow @@ -69,9 +69,23 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact" + app:layout_constraintTop_toBottomOf="@id/row_contact" tools:text="@string/risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day" /> + <de.rki.coronawarnapp.ui.view.TracingCardInfoRow + android:id="@+id/row_days_since_installation" + gone="@{!state.appInstalledForOverTwoWeeks()}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:icon="@drawable/ic_download" + android:text="@{state.getDaysSinceInstall(context)}" + android:textColor="@color/colorTextPrimary1InvertedStable" + app:compatIconTint="@color/colorStableLight" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/row_contact_last" + tools:text="@string/risk_card_body_days_since_installation" /> + <de.rki.coronawarnapp.ui.view.TracingCardInfoRow android:id="@+id/row_time_fetched" android:layout_width="0dp" @@ -82,7 +96,7 @@ app:compatIconTint="@color/colorStableLight" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact_last" + app:layout_constraintTop_toBottomOf="@id/row_days_since_installation" tools:text="@string/risk_card_body_not_yet_fetched" /> <Button @@ -95,7 +109,7 @@ android:text="@string/risk_card_button_update" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_time_fetched" /> + app:layout_constraintTop_toBottomOf="@id/row_time_fetched" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml index d89197811..b6a1b70d3 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_periodlogged_view.xml @@ -15,50 +15,51 @@ android:focusable="true" android:padding="@dimen/card_padding"> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/risk_details_period_logged_layout" - android:layout_width="match_parent" + <TextView + android:id="@+id/risk_details_period_logged_headline" + style="@style/headline5" + android:layout_width="0dp" android:layout_height="wrap_content" - android:focusable="true" + android:focusable="false" + android:text="@string/risk_details_headline_period_logged" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintTop_toTopOf="parent" /> - <TextView - android:id="@+id/risk_details_period_logged_headline" - style="@style/headline5" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:focusable="false" - android:text="@string/risk_details_headline_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + <TextView + android:id="@+id/risk_details_period_logged_subtitle" + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:focusable="false" + android:text="@string/risk_details_subtitle_period_logged" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> - <TextView - android:id="@+id/risk_details_period_logged_subtitle" - style="@style/subtitle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:focusable="false" - android:text="@string/risk_details_subtitle_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_headline" /> + <TextView + android:id="@+id/risk_details_period_logged_body_notice" + style="@style/subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" + android:text="@string/risk_details_information_body_period_logged" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> - <TextView - android:id="@+id/risk_details_period_logged_body_notice" - style="@style/body1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" - android:focusable="true" - android:text="@string/risk_details_information_body_period_logged" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_subtitle" /> - </androidx.constraintlayout.widget.ConstraintLayout> + <TextView + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:text="@{loggedPeriod.getInstallTimePeriodLogged(context)}" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/risk_details_period_logged_body_notice" + tools:text="@string/risk_details_information_body_period_logged_assessment_under_14_days" /> </androidx.constraintlayout.widget.ConstraintLayout> -</layout> \ No newline at end of file +</layout> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt index 8fb93b6e4..3bc2c760b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/LowRiskTest.kt @@ -32,6 +32,7 @@ internal class LowRiskTest { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 0, + daysSinceInstallation = 4, lastEncounterAt = null ) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt index 73be55667..4cc268ca6 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.res.Resources import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.installTime.InstallTimeProvider import de.rki.coronawarnapp.risk.ProtoRiskLevel import de.rki.coronawarnapp.risk.RiskLevelTaskResult import de.rki.coronawarnapp.risk.result.AggregatedRiskResult @@ -17,6 +18,7 @@ import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCa import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.survey.UserSurveyBox +import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -39,6 +41,7 @@ class TracingDetailsItemProviderTest : BaseTest() { @MockK lateinit var tracingStatus: GeneralTracingStatus @MockK lateinit var riskLevelStorage: RiskLevelStorage + @MockK lateinit var installTimeProvider: InstallTimeProvider @MockK lateinit var surveys: Surveys @BeforeEach @@ -50,6 +53,7 @@ class TracingDetailsItemProviderTest : BaseTest() { private fun createInstance() = TracingDetailsItemProvider( tracingStatus = tracingStatus, riskLevelStorage = riskLevelStorage, + installTimeProvider = installTimeProvider, surveys = surveys ) @@ -57,10 +61,12 @@ class TracingDetailsItemProviderTest : BaseTest() { status: GeneralTracingStatus.Status, riskLevel: ProtoRiskLevel, matchedKeyCount: Int, + daysSinceInstallation: Long, availableSurveys: List<Surveys.Type> = emptyList() ) { every { tracingStatus.generalStatus } returns flowOf(status) every { aggregatedRiskResult.totalRiskLevel } returns riskLevel + every { installTimeProvider.daysSinceInstallation } returns daysSinceInstallation every { surveys.availableSurveys } returns flowOf(availableSurveys) if (riskLevel == ProtoRiskLevel.LOW) { @@ -86,6 +92,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 1 ) @@ -102,6 +109,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -118,6 +126,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -134,6 +143,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -152,6 +162,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -170,7 +181,8 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, - matchedKeyCount = 0 + daysSinceInstallation = 4, + matchedKeyCount = 0, ) val instance = createInstance() @@ -189,6 +201,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -208,6 +221,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.UNRECOGNIZED, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -227,6 +241,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_INACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -246,6 +261,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_INACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -268,6 +284,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.UNRECOGNIZED, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -290,6 +307,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -312,6 +330,7 @@ class TracingDetailsItemProviderTest : BaseTest() { prepare( status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, + daysSinceInstallation = 4, matchedKeyCount = 0 ) @@ -335,6 +354,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.LOW, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = listOf(Surveys.Type.HIGH_RISK_ENCOUNTER) ) @@ -355,6 +375,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = emptyList() ) @@ -375,6 +396,7 @@ class TracingDetailsItemProviderTest : BaseTest() { status = GeneralTracingStatus.Status.TRACING_ACTIVE, riskLevel = ProtoRiskLevel.HIGH, matchedKeyCount = 0, + daysSinceInstallation = 4, availableSurveys = listOf(Surveys.Type.HIGH_RISK_ENCOUNTER) ) -- GitLab