Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added auth state listener to directly navigate to home when logged in #50

Merged
merged 8 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,5 @@ dependencies {
implementation(libs.navigation.compose)
implementation(libs.kotlinx.serialization.json)

implementation(libs.firebase.functions.ktx)
implementation(libs.firebase.auth.ktx)
implementation(libs.firebase.firestore.ktx)
implementation(libs.firebase.storage.ktx)

androidTestImplementation(project(":core:testing"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,53 @@ import edu.stanford.spezi.core.logging.speziLogger
import edu.stanford.spezi.core.navigation.NavigationEvent
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.module.account.AccountEvents
import edu.stanford.spezi.module.account.manager.UserSessionManager
import edu.stanford.spezi.module.account.manager.UserState
import edu.stanford.spezi.module.onboarding.OnboardingNavigationEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainActivityViewModel @Inject constructor(
private val accountEvents: AccountEvents,
private val navigator: Navigator,
private val userSessionManager: UserSessionManager,
) : ViewModel() {
private val logger by speziLogger()

init {
startObserving()
}

private fun startObserving() {
viewModelScope.launch {
accountEvents.events.collect { event ->
when (event) {
is AccountEvents.Event.SignUpSuccess, AccountEvents.Event.SignInSuccess -> {
navigator.navigateTo(AppNavigationEvent.BluetoothScreen)
}

else -> {
logger.i { "Ignoring registration event: $event" }
}
}
}
}

viewModelScope.launch {
userSessionManager.userState
.filterIsInstance<UserState.Registered>()
.collect { userState ->
val navigationEvent = if (userState.hasConsented) {
AppNavigationEvent.BluetoothScreen
} else {
OnboardingNavigationEvent.ConsentScreen
}
navigator.navigateTo(event = navigationEvent)
}
}
}

fun getNavigationEvents(): Flow<NavigationEvent> = navigator.events
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package edu.stanford.bdh.engagehf.onboarding

import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.core.utils.MessageNotifier
import edu.stanford.spezi.module.onboarding.consent.ConsentManager
import edu.stanford.spezi.module.onboarding.consent.ConsentUiState
import edu.stanford.spezi.module.onboarding.consent.PdfCreationService
import edu.stanford.spezi.module.onboarding.consent.PdfService
import javax.inject.Inject

class EngageConsentManager @Inject internal constructor(
private val pdfService: PdfService,
private val navigator: Navigator,
private val pdfCreationService: PdfCreationService,
private val messageNotifier: MessageNotifier,
) : ConsentManager {

override suspend fun getMarkdownText(): String {
Expand All @@ -23,12 +20,11 @@ class EngageConsentManager @Inject internal constructor(
""".trimIndent()
}

override suspend fun onConsented(uiState: ConsentUiState): Result<Unit> = runCatching {
val pdfBytes = pdfCreationService.createPdf(uiState)
if (pdfService.uploadPdf(pdfBytes).getOrThrow()) {
navigator.navigateTo(AppNavigationEvent.BluetoothScreen)
} else {
error("Upload went wrong")
}
override suspend fun onConsented() {
navigator.navigateTo(AppNavigationEvent.BluetoothScreen)
}

override suspend fun onConsentFailure(error: Throwable) {
messageNotifier.notify(message = "Something went wrong, failed to submit the consent!")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package edu.stanford.bdh.engagehf

import com.google.common.truth.Truth.assertThat
import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent
import edu.stanford.spezi.core.navigation.NavigationEvent
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.core.testing.CoroutineTestRule
import edu.stanford.spezi.core.testing.runTestUnconfined
import edu.stanford.spezi.module.account.AccountEvents
import edu.stanford.spezi.module.account.manager.UserSessionManager
import edu.stanford.spezi.module.account.manager.UserState
import edu.stanford.spezi.module.onboarding.OnboardingNavigationEvent
import io.mockk.Called
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.update
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class MainActivityViewModelTest {

@get:Rule
val coroutineTestRule = CoroutineTestRule()

private val accountEventsFlow = MutableSharedFlow<AccountEvents.Event>()
private val accountEvents: AccountEvents = mockk(relaxed = true)
private val navigator: Navigator = mockk(relaxed = true)
private val userStateFlow = MutableStateFlow<UserState>(UserState.NotInitialized)
private val userSessionManager: UserSessionManager = mockk()
private lateinit var viewModel: MainActivityViewModel

@Before
fun setUp() {
every { accountEvents.events } returns accountEventsFlow
every { userSessionManager.userState } returns userStateFlow
viewModel = MainActivityViewModel(
accountEvents = accountEvents,
navigator = navigator,
userSessionManager = userSessionManager,
)
}

@Test
fun `it should start observing on init`() {
verify { accountEvents.events }
verify { userSessionManager.userState }
}

@Test
fun `it should navigate to bluetooth screen on SignUpSuccess event`() = runTestUnconfined {
// given
val event = AccountEvents.Event.SignUpSuccess

// when
accountEventsFlow.emit(event)

// then
verify { navigator.navigateTo(event = AppNavigationEvent.BluetoothScreen) }
}

@Test
fun `it should navigate to bluetooth screen on SignInSuccess event`() = runTestUnconfined {
// given
val event = AccountEvents.Event.SignInSuccess

// when
accountEventsFlow.emit(event)

// then
verify { navigator.navigateTo(event = AppNavigationEvent.BluetoothScreen) }
}

@Test
fun `it should not navigate on other account events`() = runTestUnconfined {
// given
val event = AccountEvents.Event.SignInFailure

// when
accountEventsFlow.emit(event)

// then
verify { navigator wasNot Called }
}

@Test
fun `it should return navigation events`() {
// given
val events: SharedFlow<NavigationEvent> = mockk()
every { navigator.events } returns events

// when
val result = viewModel.getNavigationEvents()

// then
assertThat(result).isEqualTo(events)
}

@Test
fun `it should navigate to bluetooth screen for registered user if consented`() =
runTestUnconfined {
// given
val userState = UserState.Registered(hasConsented = true)

// when
userStateFlow.update { userState }

// then
verify { navigator.navigateTo(event = AppNavigationEvent.BluetoothScreen) }
}

@Test
fun `it should navigate to consent screen for registered user if not consented`() =
runTestUnconfined {
// given
val userState = UserState.Registered(hasConsented = false)

// when
userStateFlow.update { userState }

// then
verify { navigator.navigateTo(event = OnboardingNavigationEvent.ConsentScreen) }
}

@Test
fun `it should not navigate for not initialized users`() =
runTestUnconfined {
// given
val userState = UserState.NotInitialized

// when
userStateFlow.update { userState }

// then
verify { navigator wasNot Called }
}

@Test
fun `it should not navigate for anonymous users`() =
runTestUnconfined {
// given
val userState = UserState.Anonymous

// when
userStateFlow.update { userState }

// then
verify { navigator wasNot Called }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package edu.stanford.bdh.engagehf.onboarding

import com.google.common.truth.Truth.assertThat
import edu.stanford.bdh.engagehf.navigation.AppNavigationEvent
import edu.stanford.spezi.core.navigation.Navigator
import edu.stanford.spezi.core.testing.runTestUnconfined
import edu.stanford.spezi.core.utils.MessageNotifier
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import org.junit.Before
import org.junit.Test

class EngageConsentManagerTest {
private val navigator: Navigator = mockk()
private val messageNotifier: MessageNotifier = mockk()
private val manager = EngageConsentManager(
navigator = navigator,
messageNotifier = messageNotifier,
)

@Before
fun setup() {
every { navigator.navigateTo(AppNavigationEvent.BluetoothScreen) } just Runs
every { messageNotifier.notify(any(), any()) } just Runs
}

@Test
fun `it should return the correct markdown test`() = runTestUnconfined {
// given
val expectedText = """
# Consent
The ENGAGE-HF Android Mobile Application will connect to external devices via Bluetooth to record personal health information, including weight, heart rate, and blood pressure.

Your personal information will only be shared with the research team conducting the study.
""".trimIndent()

// when
val result = manager.getMarkdownText()

// then
assertThat(result).isEqualTo(expectedText)
}

@Test
fun `it should navigate to bluetooth screen on consented`() = runTestUnconfined {
// given
val navigationEvent = AppNavigationEvent.BluetoothScreen

// when
manager.onConsented()

// then
verify { navigator.navigateTo(event = navigationEvent) }
}

@Test
fun `it should notify error message on on consent failure`() = runTestUnconfined {
// given
val message = "Something went wrong, failed to submit the consent!"

// when
manager.onConsentFailure(error = mockk())

// then
verify { messageNotifier.notify(message = message) }
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package edu.stanford.spezi.core.bluetooth.domain

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
Expand Down Expand Up @@ -34,7 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean
* @property scope The coroutine scope used for launching connection events and mapping measurements.
* @property context The application context.
*/
@SuppressLint("MissingPermission")
@Suppress("DEPRECATION", "MissingPermission")
internal class BLEDeviceConnector @AssistedInject constructor(
@Assisted private val device: BluetoothDevice,
private val measurementMapper: MeasurementMapper,
Expand Down
Loading
Loading