BIT-543: Add Remember Me functionality to Landing Screen (#104)

Co-authored-by: Brian Yencho <brian@livefront.com>
This commit is contained in:
Andrew Haisting
2023-10-10 13:22:41 -05:00
committed by Álison Fernandes
parent c7ab805f91
commit 5a2a2f93f3
12 changed files with 306 additions and 28 deletions

View File

@@ -0,0 +1,11 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
/**
* Primary access point for disk information.
*/
interface AuthDiskSource {
/**
* The currently persisted saved email address (or `null` if not set).
*/
var rememberedEmailAddress: String?
}

View File

@@ -0,0 +1,21 @@
package com.x8bit.bitwarden.data.auth.datasource.disk
import android.content.SharedPreferences
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "bwPreferencesStorage:rememberedEmail"
/**
* Primary implementation of [AuthDiskSource].
*/
class AuthDiskSourceImpl(
private val sharedPreferences: SharedPreferences,
) : AuthDiskSource {
override var rememberedEmailAddress: String?
get() = sharedPreferences.getString(REMEMBERED_EMAIL_ADDRESS_KEY, null)
set(value) {
sharedPreferences
.edit()
.putString(REMEMBERED_EMAIL_ADDRESS_KEY, value)
.apply()
}
}

View File

@@ -0,0 +1,25 @@
package com.x8bit.bitwarden.data.auth.datasource.disk.di
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides persistence-related dependencies in the auth package.
*/
@Module
@InstallIn(SingletonComponent::class)
object DiskModule {
@Provides
@Singleton
fun provideAuthDiskSource(
sharedPreferences: SharedPreferences,
): AuthDiskSource =
AuthDiskSourceImpl(sharedPreferences = sharedPreferences)
}

View File

@@ -21,6 +21,11 @@ interface AuthRepository {
*/
val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult>
/**
* The currently persisted saved email address (or `null` if not set).
*/
var rememberedEmailAddress: String?
/**
* Attempt to login with the given email and password. Updated access token will be reflected
* in [authStateFlow].

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository
import com.bitwarden.core.Kdf
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.CaptchaRequired
@@ -21,6 +22,8 @@ import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "bwPreferencesStorage:rememberedEmail"
/**
* Default implementation of [AuthRepository].
*/
@@ -29,6 +32,7 @@ class AuthRepositoryImpl @Inject constructor(
private val accountsService: AccountsService,
private val identityService: IdentityService,
private val bitwardenSdkClient: Client,
private val authDiskSource: AuthDiskSource,
private val authTokenInterceptor: AuthTokenInterceptor,
) : AuthRepository {
@@ -40,6 +44,12 @@ class AuthRepositoryImpl @Inject constructor(
override val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult> =
mutableCaptchaTokenFlow.asSharedFlow()
override var rememberedEmailAddress: String?
get() = authDiskSource.rememberedEmailAddress
set(value) {
authDiskSource.rememberedEmailAddress = value
}
override suspend fun login(
email: String,
password: String,

View File

@@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.platform.datasource.di
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides dependencies related to encryption / decryption / secure generation.
*/
@Module
@InstallIn(SingletonComponent::class)
object PreferenceModule {
@Provides
@Singleton
fun provideDefaultSharedPreferences(
application: Application,
): SharedPreferences = application.getSharedPreferences(null, Context.MODE_PRIVATE)
}

View File

@@ -32,10 +32,8 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
@@ -43,7 +41,6 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
/**
* The top level composable for the Landing screen.
@@ -234,15 +231,3 @@ private fun RegionSelector(
}
}
}
@Preview
@Composable
private fun LandingScreen_preview() {
BitwardenTheme {
LandingScreen(
onNavigateToCreateAccount = {},
onNavigateToLogin = { _, _ -> },
viewModel = LandingViewModel(SavedStateHandle()),
)
}
}

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
@@ -18,13 +19,14 @@ private const val KEY_STATE = "state"
*/
@HiltViewModel
class LandingViewModel @Inject constructor(
private val authRepository: AuthRepository,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<LandingState, LandingEvent, LandingAction>(
initialState = savedStateHandle[KEY_STATE]
?: LandingState(
emailInput = "",
isContinueButtonEnabled = false,
isRememberMeEnabled = false,
emailInput = authRepository.rememberedEmailAddress.orEmpty(),
isContinueButtonEnabled = authRepository.rememberedEmailAddress != null,
isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
selectedRegion = LandingState.RegionOption.BITWARDEN_US,
),
) {
@@ -61,8 +63,14 @@ class LandingViewModel @Inject constructor(
if (mutableStateFlow.value.emailInput.isBlank()) {
return
}
val email = mutableStateFlow.value.emailInput
val isRememberMeEnabled = mutableStateFlow.value.isRememberMeEnabled
val selectedRegionLabel = mutableStateFlow.value.selectedRegion.label
// Update the remembered email address
authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled }
sendEvent(LandingEvent.NavigateToLogin(email, selectedRegionLabel))
}