Skip to content

Commit

Permalink
added auth state listener to directly navigate to home when logged in (
Browse files Browse the repository at this point in the history
…#50)

# *auth state listener*

## ♻️ Current situation & Problem
#37 


## ⚙️ Release Notes 

- added auth state listener to directly navigate to home when logged in

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).

---------

Signed-off-by: Basler182 <[email protected]>
Co-authored-by: eldcn <[email protected]>
  • Loading branch information
Basler182 and eldcn authored Jul 8, 2024
1 parent 1c2559c commit d2701ef
Show file tree
Hide file tree
Showing 38 changed files with 727 additions and 245 deletions.
7 changes: 2 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ dependencies {
implementation(project(":modules:education"))
implementation(project(":modules:onboarding"))

implementation(libs.firebase.firestore.ktx)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.view.model.ktx)
Expand All @@ -53,10 +55,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 @@ -11,9 +11,13 @@ 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.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
Expand All @@ -23,6 +27,7 @@ import edu.stanford.spezi.core.design.R as DesignR
class MainActivityViewModel @Inject constructor(
private val accountEvents: AccountEvents,
private val navigator: Navigator,
private val userSessionManager: UserSessionManager,
) : ViewModel() {
private val logger by speziLogger()

Expand All @@ -36,6 +41,10 @@ class MainActivityViewModel @Inject constructor(
val uiState = _uiState.asStateFlow()

init {
startObserving()
}

private fun startObserving() {
viewModelScope.launch {
accountEvents.events.collect { event ->
when (event) {
Expand All @@ -49,6 +58,19 @@ class MainActivityViewModel @Inject constructor(
}
}
}

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

fun onAction(action: Action) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package edu.stanford.bdh.engagehf.education

import com.google.firebase.firestore.FirebaseFirestore
import edu.stanford.spezi.module.onboarding.invitation.await
import edu.stanford.spezi.modules.education.videos.VideoSection
import edu.stanford.spezi.modules.education.videos.data.repository.EducationRepository
import kotlinx.coroutines.tasks.await
import javax.inject.Inject

class EngageEducationRepository @Inject constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package edu.stanford.bdh.engagehf.education

import com.google.firebase.firestore.DocumentSnapshot
import edu.stanford.spezi.module.onboarding.invitation.await
import edu.stanford.spezi.modules.education.videos.Video
import edu.stanford.spezi.modules.education.videos.VideoSection
import kotlinx.coroutines.tasks.await
import java.util.Locale
import javax.inject.Inject

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.stanford.bdh.engagehf.navigation.screens

import androidx.activity.ComponentActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
Expand All @@ -12,6 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
Expand All @@ -27,7 +29,9 @@ import edu.stanford.spezi.modules.education.videos.EducationScreen

@Composable
fun AppScreen() {
val viewModel = hiltViewModel<MainActivityViewModel>()
val viewModel = hiltViewModel<MainActivityViewModel>(
viewModelStoreOwner = LocalContext.current as ComponentActivity
)
val uiState by viewModel.uiState.collectAsState()
AppScreen(
uiState = uiState,
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.AppScreen)
} else {
error("Upload went wrong")
}
override suspend fun onConsented() {
navigator.navigateTo(AppNavigationEvent.AppScreen)
}

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
@@ -1,32 +1,157 @@
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 kotlinx.coroutines.ExperimentalCoroutinesApi
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

@ExperimentalCoroutinesApi
class MainActivityViewModelTest {

private var mockAccountEvents: AccountEvents = mockk(relaxed = true)
private var mockNavigator: Navigator = mockk(relaxed = true)

private lateinit var viewModel: MainActivityViewModel

@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() {
viewModel = MainActivityViewModel(mockAccountEvents, mockNavigator)
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 app screen on SignUpSuccess event`() = runTestUnconfined {
// given
val event = AccountEvents.Event.SignUpSuccess

// when
accountEventsFlow.emit(event)

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

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

// when
accountEventsFlow.emit(event)

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

@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 app screen for registered user if consented`() =
runTestUnconfined {
// given
val userState = UserState.Registered(hasConsented = true)

// when
userStateFlow.update { userState }

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

@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 }
}

@Test
fun `given selectedItem when onAction UpdateSelectedItem then uiState selectedItem should be updated`() =
runTestUnconfined {
Expand Down
Loading

0 comments on commit d2701ef

Please sign in to comment.