mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
Update the current user's last active time when navigating (#606)
This commit is contained in:
parent
3def25366b
commit
056b6eb30c
9 changed files with 119 additions and 7 deletions
|
@ -72,6 +72,11 @@ interface AuthRepository : AuthenticatorProvider {
|
|||
*/
|
||||
fun switchAccount(userId: String): SwitchAccountResult
|
||||
|
||||
/**
|
||||
* Updates the "last active time" for the current user.
|
||||
*/
|
||||
fun updateLastActiveTime()
|
||||
|
||||
/**
|
||||
* Attempt to register a new account with the given parameters.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository
|
||||
|
||||
import android.os.SystemClock
|
||||
import com.bitwarden.core.HashPurpose
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
|
@ -55,7 +56,7 @@ import javax.inject.Singleton
|
|||
/**
|
||||
* Default implementation of [AuthRepository].
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@Singleton
|
||||
class AuthRepositoryImpl constructor(
|
||||
private val accountsService: AccountsService,
|
||||
|
@ -68,6 +69,7 @@ class AuthRepositoryImpl constructor(
|
|||
private val vaultRepository: VaultRepository,
|
||||
private val userLogoutManager: UserLogoutManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() },
|
||||
) : AuthRepository {
|
||||
private val mutableSpecialCircumstanceStateFlow =
|
||||
MutableStateFlow<UserState.SpecialCircumstance?>(null)
|
||||
|
@ -294,6 +296,14 @@ class AuthRepositoryImpl constructor(
|
|||
return SwitchAccountResult.AccountSwitched
|
||||
}
|
||||
|
||||
override fun updateLastActiveTime() {
|
||||
val userId = activeUserId ?: return
|
||||
authDiskSource.storeLastActiveTimeMillis(
|
||||
userId = userId,
|
||||
lastActiveTimeMillis = elapsedRealtimeMillisProvider(),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount", "LongMethod")
|
||||
override suspend fun register(
|
||||
email: String,
|
||||
|
|
|
@ -24,6 +24,8 @@ import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.VAULT_UNLOCKED_GRAP
|
|||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.navigateToVaultUnlockedGraph
|
||||
import com.x8bit.bitwarden.ui.platform.feature.vaultunlocked.vaultUnlockedGraph
|
||||
import com.x8bit.bitwarden.ui.platform.theme.RootTransitionProviders
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
|
@ -43,6 +45,15 @@ fun RootNavScreen(
|
|||
if (isNotSplashScreen) onSplashScreenRemoved()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
navController
|
||||
.currentBackStackEntryFlow
|
||||
.onEach {
|
||||
viewModel.trySendAction(RootNavAction.BackStackUpdate)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = SPLASH_ROUTE,
|
||||
|
|
|
@ -19,7 +19,7 @@ private const val KEY_NAV_DESTINATION = "nav_state"
|
|||
*/
|
||||
@HiltViewModel
|
||||
class RootNavViewModel @Inject constructor(
|
||||
authRepository: AuthRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
) : BaseViewModel<RootNavState, Unit, RootNavAction>(
|
||||
initialState = RootNavState.Splash,
|
||||
) {
|
||||
|
@ -32,10 +32,15 @@ class RootNavViewModel @Inject constructor(
|
|||
|
||||
override fun handleAction(action: RootNavAction) {
|
||||
when (action) {
|
||||
is RootNavAction.BackStackUpdate -> handleBackStackUpdate()
|
||||
is RootNavAction.Internal.UserStateUpdateReceive -> handleUserStateUpdateReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackStackUpdate() {
|
||||
authRepository.updateLastActiveTime()
|
||||
}
|
||||
|
||||
private fun handleUserStateUpdateReceive(
|
||||
action: RootNavAction.Internal.UserStateUpdateReceive,
|
||||
) {
|
||||
|
@ -93,6 +98,11 @@ sealed class RootNavState : Parcelable {
|
|||
*/
|
||||
sealed class RootNavAction {
|
||||
|
||||
/**
|
||||
* Indicates the backstack has changed.
|
||||
*/
|
||||
data object BackStackUpdate : RootNavAction()
|
||||
|
||||
/**
|
||||
* Internal ViewModel actions.
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ import androidx.compose.material3.NavigationBarItemDefaults
|
|||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -57,6 +58,8 @@ import com.x8bit.bitwarden.ui.tools.feature.send.sendGraph
|
|||
import com.x8bit.bitwarden.ui.vault.feature.vault.VAULT_GRAPH_ROUTE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.navigateToVaultGraph
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.vaultGraph
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
|
@ -98,6 +101,15 @@ fun VaultUnlockedNavBarScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
navController
|
||||
.currentBackStackEntryFlow
|
||||
.onEach {
|
||||
viewModel.trySendAction(VaultUnlockedNavBarAction.BackStackUpdate)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
VaultUnlockedNavBarScaffold(
|
||||
navController = navController,
|
||||
onNavigateToVaultItem = onNavigateToVaultItem,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
@ -8,7 +9,9 @@ import javax.inject.Inject
|
|||
* Manages bottom tab navigation of the application.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class VaultUnlockedNavBarViewModel @Inject constructor() :
|
||||
class VaultUnlockedNavBarViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
) :
|
||||
BaseViewModel<Unit, VaultUnlockedNavBarEvent, VaultUnlockedNavBarAction>(
|
||||
initialState = Unit,
|
||||
) {
|
||||
|
@ -19,6 +22,7 @@ class VaultUnlockedNavBarViewModel @Inject constructor() :
|
|||
VaultUnlockedNavBarAction.SendTabClick -> handleSendTabClicked()
|
||||
VaultUnlockedNavBarAction.SettingsTabClick -> handleSettingsTabClicked()
|
||||
VaultUnlockedNavBarAction.VaultTabClick -> handleVaultTabClicked()
|
||||
VaultUnlockedNavBarAction.BackStackUpdate -> handleBackStackUpdate()
|
||||
}
|
||||
}
|
||||
// #region BottomTabViewModel Action Handlers
|
||||
|
@ -49,6 +53,10 @@ class VaultUnlockedNavBarViewModel @Inject constructor() :
|
|||
private fun handleSettingsTabClicked() {
|
||||
sendEvent(VaultUnlockedNavBarEvent.NavigateToSettingsScreen)
|
||||
}
|
||||
|
||||
private fun handleBackStackUpdate() {
|
||||
authRepository.updateLastActiveTime()
|
||||
}
|
||||
// #endregion BottomTabViewModel Action Handlers
|
||||
}
|
||||
|
||||
|
@ -75,6 +83,11 @@ sealed class VaultUnlockedNavBarAction {
|
|||
* click Settings tab.
|
||||
*/
|
||||
data object SettingsTabClick : VaultUnlockedNavBarAction()
|
||||
|
||||
/**
|
||||
* Indicates the backstack has changed.
|
||||
*/
|
||||
data object BackStackUpdate : VaultUnlockedNavBarAction()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -125,6 +125,8 @@ class AuthRepositoryTest {
|
|||
every { logout(any()) } just runs
|
||||
}
|
||||
|
||||
private var elapsedRealtimeMillis = 123456789L
|
||||
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
identityService = identityService,
|
||||
|
@ -136,6 +138,7 @@ class AuthRepositoryTest {
|
|||
vaultRepository = vaultRepository,
|
||||
userLogoutManager = userLogoutManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
elapsedRealtimeMillisProvider = { elapsedRealtimeMillis },
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
|
@ -1220,6 +1223,21 @@ class AuthRepositoryTest {
|
|||
verify { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateLastActiveTime should update the last active time for the current user`() {
|
||||
val userId = USER_ID_1
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
|
||||
assertNull(fakeAuthDiskSource.getLastActiveTimeMillis(userId = userId))
|
||||
|
||||
repository.updateLastActiveTime()
|
||||
|
||||
assertEquals(
|
||||
elapsedRealtimeMillis,
|
||||
fakeAuthDiskSource.getLastActiveTimeMillis(userId = userId),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getPasswordBreachCount should return failure when service returns failure`() = runTest {
|
||||
val password = "password"
|
||||
|
|
|
@ -5,7 +5,10 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
|||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -14,6 +17,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { updateLastActiveTime() } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -74,6 +78,13 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(RootNavState.VaultLocked, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackStackUpdate should call updateLastActiveTime`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(RootNavAction.BackStackUpdate)
|
||||
verify { authRepository.updateLastActiveTime() }
|
||||
}
|
||||
|
||||
private fun createViewModel(): RootNavViewModel =
|
||||
RootNavViewModel(
|
||||
authRepository = authRepository,
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
||||
private val authRepository: AuthRepository = mockk {
|
||||
every { updateLastActiveTime() } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `VaultTabClick should navigate to the vault screen`() = runTest {
|
||||
val viewModel = VaultUnlockedNavBarViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultUnlockedNavBarAction.VaultTabClick)
|
||||
assertEquals(VaultUnlockedNavBarEvent.NavigateToVaultScreen, awaitItem())
|
||||
|
@ -18,7 +28,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `SendTabClick should navigate to the send screen`() = runTest {
|
||||
val viewModel = VaultUnlockedNavBarViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultUnlockedNavBarAction.SendTabClick)
|
||||
assertEquals(VaultUnlockedNavBarEvent.NavigateToSendScreen, awaitItem())
|
||||
|
@ -27,7 +37,7 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `GeneratorTabClick should navigate to the generator screen`() = runTest {
|
||||
val viewModel = VaultUnlockedNavBarViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultUnlockedNavBarAction.GeneratorTabClick)
|
||||
assertEquals(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen, awaitItem())
|
||||
|
@ -36,10 +46,22 @@ class VaultUnlockedNavBarViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `SettingsTabClick should navigate to the settings screen`() = runTest {
|
||||
val viewModel = VaultUnlockedNavBarViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultUnlockedNavBarAction.SettingsTabClick)
|
||||
assertEquals(VaultUnlockedNavBarEvent.NavigateToSettingsScreen, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackStackUpdate should call updateLastActiveTime`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultUnlockedNavBarAction.BackStackUpdate)
|
||||
verify { authRepository.updateLastActiveTime() }
|
||||
}
|
||||
|
||||
private fun createViewModel() =
|
||||
VaultUnlockedNavBarViewModel(
|
||||
authRepository = authRepository,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue