Skip to content
Snippets Groups Projects
Commit 38c1a447 authored by Mohamed Metwalli's avatar Mohamed Metwalli
Browse files

Merge branch 'release/1.11.x' into feature/4559-contact-journal-screenshot

parents f1626713 a36cf7ee
No related branches found
No related tags found
No related merge requests found
Showing
with 156 additions and 63 deletions
......@@ -68,7 +68,7 @@ class SubmissionStateProvider @Inject constructor(
fun isFetching(): Boolean =
isDeviceRegistered && when (deviceUiState) {
is NetworkRequestWrapper.RequestFailed -> deviceUiState.error is CwaServerError
is NetworkRequestWrapper.RequestFailed -> false
is NetworkRequestWrapper.RequestStarted -> true
is NetworkRequestWrapper.RequestIdle -> true
else -> false
......@@ -112,11 +112,13 @@ class SubmissionStateProvider @Inject constructor(
}
fun isPending(): Boolean =
deviceUiState.withSuccess(false) {
when (it) {
DeviceUIState.PAIRED_ERROR, DeviceUIState.PAIRED_NO_RESULT -> true
else -> false
when (deviceUiState) {
is NetworkRequestWrapper.RequestFailed -> true
is NetworkRequestWrapper.RequestSuccessful -> {
deviceUiState.data == DeviceUIState.PAIRED_ERROR ||
deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT
}
else -> false
}
}
}
package de.rki.coronawarnapp.ui.submission.testresult.pending
import android.app.AlertDialog
import android.os.Bundle
import android.view.View
import android.view.accessibility.AccessibilityEvent
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPendingBinding
......@@ -12,11 +12,11 @@ import de.rki.coronawarnapp.exception.http.CwaServerError
import de.rki.coronawarnapp.exception.http.CwaWebException
import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat
import de.rki.coronawarnapp.util.DialogHelper
import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withFailure
import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess
import de.rki.coronawarnapp.util.NetworkRequestWrapper
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.doNavigate
import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.observeOnce
import de.rki.coronawarnapp.util.ui.popBackStack
import de.rki.coronawarnapp.util.ui.setInvisible
import de.rki.coronawarnapp.util.ui.viewBindingLazy
......@@ -33,6 +33,8 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
private var skipInitialTestResultRefresh = false
private var errorDialog: AlertDialog? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
......@@ -41,19 +43,10 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
}
pendingViewModel.testState.observe2(this) { result ->
result.deviceUiState.withFailure {
if (it is CwaWebException) {
DialogHelper.showDialog(buildErrorDialog(it))
}
}
val hasResult = result.deviceUiState.withSuccess(false) { true }
val hasResult = result.deviceUiState is NetworkRequestWrapper.RequestSuccessful
binding.apply {
submissionTestResultSection.setTestResultSection(result.deviceUiState, result.testResultReceivedDate)
submissionTestResultSpinner.setInvisible(hasResult)
submissionTestResultContent.setInvisible(!hasResult)
buttonContainer.setInvisible(!hasResult)
}
......@@ -98,8 +91,16 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
super.onResume()
binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
pendingViewModel.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh)
skipInitialTestResultRefresh = false
pendingViewModel.cwaWebExceptionLiveData.observeOnce(this.viewLifecycleOwner) { exception ->
handleError(exception)
}
}
override fun onPause() {
pendingViewModel.cwaWebExceptionLiveData.removeObservers(this.viewLifecycleOwner)
errorDialog?.dismiss()
super.onPause()
}
private fun removeTestAfterConfirmation() {
......@@ -118,33 +119,42 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio
}
}
private fun handleError(exception: CwaWebException) {
errorDialog = when (exception) {
is CwaClientError, is CwaServerError -> {
DialogHelper.showDialog(buildErrorDialog(exception))
}
else -> {
DialogHelper.showDialog(genericErrorDialog)
}
}
}
private fun navigateToMainScreen() {
popBackStack()
}
private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance {
return when (exception) {
is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
requireActivity(),
R.string.submission_error_dialog_web_generic_error_title,
getString(
R.string.submission_error_dialog_web_generic_network_error_body,
exception.statusCode
),
R.string.submission_error_dialog_web_generic_error_button_positive,
null,
true,
::navigateToMainScreen
)
else -> DialogHelper.DialogInstance(
requireActivity(),
R.string.submission_error_dialog_web_generic_error_title,
R.string.submission_error_dialog_web_generic_error_body,
R.string.submission_error_dialog_web_generic_error_button_positive,
null,
true,
::navigateToMainScreen
)
}
}
private fun buildErrorDialog(exception: CwaWebException) = DialogHelper.DialogInstance(
requireActivity(),
R.string.submission_error_dialog_web_generic_error_title,
getString(
R.string.submission_error_dialog_web_generic_network_error_body,
exception.statusCode
),
R.string.submission_error_dialog_web_generic_error_button_positive,
null,
true,
::navigateToMainScreen
)
private val genericErrorDialog: DialogHelper.DialogInstance
get() = DialogHelper.DialogInstance(
requireActivity(),
R.string.submission_error_dialog_web_generic_error_title,
R.string.submission_error_dialog_web_generic_error_body,
R.string.submission_error_dialog_web_generic_error_button_positive,
null,
true,
::navigateToMainScreen
)
}
package de.rki.coronawarnapp.ui.submission.testresult.pending
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.navigation.NavDirections
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.exception.http.CwaWebException
import de.rki.coronawarnapp.notification.TestResultNotificationService
import de.rki.coronawarnapp.submission.SubmissionRepository
import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState
......@@ -16,7 +16,9 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
......@@ -57,7 +59,8 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
testResultReceivedDate = resultDate
)
}
val testState: LiveData<TestResultUIState> = testResultFlow
val testState = testResultFlow
.onEach { testResultUIState ->
testResultUIState.deviceUiState.withSuccess { deviceState ->
when (deviceState) {
......@@ -85,9 +88,10 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
}
.asLiveData(context = dispatcherProvider.Default)
fun onTestOpened() {
submissionRepository.setViewedTestResult()
}
val cwaWebExceptionLiveData = submissionRepository.deviceUIStateFlow
.filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>>()
.map { it.error }
.asLiveData()
fun observeTestResultToSchedulePositiveTestResultReminder() = launch {
submissionRepository.deviceUIStateFlow
......@@ -103,7 +107,6 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor(
Timber.d("deregisterTestFromDevice()")
launch {
submissionRepository.removeTestFromDevice()
routeToScreen.postValue(null)
}
}
......
......@@ -3,7 +3,7 @@ package de.rki.coronawarnapp.util
import android.content.Context
import android.net.wifi.WifiManager
import android.os.PowerManager
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask
import de.rki.coronawarnapp.storage.LocalData
......@@ -12,6 +12,7 @@ import de.rki.coronawarnapp.task.common.DefaultTaskRequest
import de.rki.coronawarnapp.task.submitBlocking
import de.rki.coronawarnapp.util.device.BackgroundModeStatus
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.di.ProcessLifecycle
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
......@@ -25,7 +26,8 @@ import javax.inject.Singleton
class WatchdogService @Inject constructor(
@AppContext private val context: Context,
private val taskController: TaskController,
private val backgroundModeStatus: BackgroundModeStatus
private val backgroundModeStatus: BackgroundModeStatus,
@ProcessLifecycle private val processLifecycleOwner: LifecycleOwner
) {
private val powerManager by lazy {
......@@ -44,7 +46,7 @@ class WatchdogService @Inject constructor(
}
Timber.tag(TAG).v("Acquiring wakelocks for watchdog routine.")
ProcessLifecycleOwner.get().lifecycleScope.launch {
processLifecycleOwner.lifecycleScope.launch {
// A wakelock as the OS does not handle this for us like in the background job execution
val wakeLock = createWakeLock()
// A wifi lock to wake up the wifi connection in case the device is dozing
......
......@@ -2,9 +2,9 @@ package de.rki.coronawarnapp.util.device
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.util.di.ProcessLifecycle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onCompletion
......@@ -15,7 +15,9 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ForegroundState @Inject constructor() {
class ForegroundState @Inject constructor(
@ProcessLifecycle val processLifecycleOwner: LifecycleOwner
) {
val isInForeground: Flow<Boolean> by lazy {
MutableStateFlow(false).apply {
......@@ -23,19 +25,19 @@ class ForegroundState @Inject constructor() {
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {
CoronaWarnApplication.isAppInForeground = true
Timber.v("App is in the foreground")
tryEmit(true)
}
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
CoronaWarnApplication.isAppInForeground = false
Timber.v("App is in the background")
tryEmit(false)
}
}
val processLifecycle = ProcessLifecycleOwner.get().lifecycle
val processLifecycle = processLifecycleOwner.lifecycle
processLifecycle.addObserver(foregroundStateUpdater)
}
.onStart { Timber.v("isInForeground FLOW start") }
......
......@@ -8,6 +8,8 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.navigation.NavDeepLinkBuilder
import androidx.work.WorkManager
import dagger.Module
......@@ -63,4 +65,9 @@ class AndroidModule {
@Provides
@Singleton
fun activityManager(@AppContext context: Context): ActivityManager = context.getSystemService()!!
@Provides
@Singleton
@ProcessLifecycle
fun procressLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
}
package de.rki.coronawarnapp.util.di
import javax.inject.Qualifier
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class ProcessLifecycle
......@@ -4,15 +4,18 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.observe
fun <T> LiveData<T>.observe2(fragment: Fragment, callback: (T) -> Unit) {
observe(fragment.viewLifecycleOwner, { callback.invoke(it) })
observe(fragment.viewLifecycleOwner) {
callback.invoke(it)
}
}
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, observer: Observer<T>) {
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, onValueChanged: (t: T) -> Unit) {
val internalObserver = object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
override fun onChanged(t: T) {
onValueChanged(t)
removeObserver(this)
}
}
......
package de.rki.coronawarnapp.util.device
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.coroutines.test
class ForegroundStateTest : BaseTest() {
@MockK lateinit var lifecycleOwner: LifecycleOwner
lateinit var lifecycle: LifecycleRegistry
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
lifecycle = LifecycleRegistry(lifecycleOwner)
every { lifecycleOwner.lifecycle } returns lifecycle
}
@AfterEach
fun teardown() {
clearAllMocks()
}
fun createInstance() = ForegroundState(
processLifecycleOwner = lifecycleOwner
)
@Test
fun `test emissions`() = runBlockingTest {
val instance = createInstance()
val testCollector = instance.isInForeground.test(startOnScope = this)
testCollector.latestValue shouldBe false
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
testCollector.latestValue shouldBe true
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
testCollector.latestValue shouldBe false
testCollector.cancel()
advanceUntilIdle()
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment