mirror of
https://github.com/bitwarden/android.git
synced 2024-11-28 13:58:51 +03:00
PM-10242 PM-10243 PM-10244 PM-10245 PM-10246: Welcome carousel (#3657)
Some checks failed
Crowdin Push / Crowdin Push (push) Waiting to run
Scan / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Check PR run (push) Failing after 0s
Test / Test (push) Has been skipped
Some checks failed
Crowdin Push / Crowdin Push (push) Waiting to run
Scan / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Check PR run (push) Failing after 0s
Test / Test (push) Has been skipped
This commit is contained in:
parent
e598fe5714
commit
f17289a104
19 changed files with 1026 additions and 8 deletions
|
@ -130,6 +130,12 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
|||
*/
|
||||
val organizations: List<SyncResponseJson.Profile.Organization>
|
||||
|
||||
/**
|
||||
* Whether or not the welcome carousel should be displayed, based on the feature flag and
|
||||
* whether the user has ever logged in or created an account before.
|
||||
*/
|
||||
val showWelcomeCarousel: Boolean
|
||||
|
||||
/**
|
||||
* Clears the pending deletion state that occurs when the an account is successfully deleted.
|
||||
*/
|
||||
|
|
|
@ -77,9 +77,11 @@ import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
|
|||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
@ -140,6 +142,7 @@ class AuthRepositoryImpl(
|
|||
private val trustedDeviceManager: TrustedDeviceManager,
|
||||
private val userLogoutManager: UserLogoutManager,
|
||||
private val policyManager: PolicyManager,
|
||||
private val featureFlagManager: FeatureFlagManager,
|
||||
pushManager: PushManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : AuthRepository,
|
||||
|
@ -318,6 +321,10 @@ class AuthRepositoryImpl(
|
|||
override val organizations: List<SyncResponseJson.Profile.Organization>
|
||||
get() = activeUserId?.let { authDiskSource.getOrganizations(it) }.orEmpty()
|
||||
|
||||
override val showWelcomeCarousel: Boolean
|
||||
get() = !settingsRepository.hasUserLoggedInOrCreatedAccount &&
|
||||
featureFlagManager.getFeatureFlag(FlagKey.OnboardingCarousel)
|
||||
|
||||
init {
|
||||
pushManager
|
||||
.syncOrgKeysFlow
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
|
|||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
|
@ -52,6 +53,7 @@ object AuthRepositoryModule {
|
|||
userLogoutManager: UserLogoutManager,
|
||||
pushManager: PushManager,
|
||||
policyManager: PolicyManager,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
): AuthRepository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
devicesService = devicesService,
|
||||
|
@ -70,5 +72,6 @@ object AuthRepositoryModule {
|
|||
userLogoutManager = userLogoutManager,
|
||||
pushManager = pushManager,
|
||||
policyManager = policyManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.x8bit.bitwarden.ui.auth.feature.environment.environmentDestination
|
|||
import com.x8bit.bitwarden.ui.auth.feature.environment.navigateToEnvironment
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.LANDING_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.landingDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.landing.navigateToLanding
|
||||
import com.x8bit.bitwarden.ui.auth.feature.login.loginDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.login.navigateToLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.loginwithdevice.loginWithDeviceDestination
|
||||
|
@ -25,6 +26,7 @@ import com.x8bit.bitwarden.ui.auth.feature.setpassword.navigateToSetPassword
|
|||
import com.x8bit.bitwarden.ui.auth.feature.setpassword.setPasswordDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.navigateToTwoFactorLogin
|
||||
import com.x8bit.bitwarden.ui.auth.feature.twofactorlogin.twoFactorLoginDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.welcome.welcomeDestination
|
||||
|
||||
const val AUTH_GRAPH_ROUTE: String = "auth_graph"
|
||||
|
||||
|
@ -32,7 +34,9 @@ const val AUTH_GRAPH_ROUTE: String = "auth_graph"
|
|||
* Add auth destinations to the nav graph.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
||||
fun NavGraphBuilder.authGraph(
|
||||
navController: NavHostController,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = LANDING_ROUTE,
|
||||
route = AUTH_GRAPH_ROUTE,
|
||||
|
@ -72,6 +76,10 @@ fun NavGraphBuilder.authGraph(navController: NavHostController) {
|
|||
navController.navigateToEnvironment()
|
||||
},
|
||||
)
|
||||
welcomeDestination(
|
||||
onNavigateToCreateAccount = { navController.navigateToCreateAccount() },
|
||||
onNavigateToLogin = { navController.navigateToLanding() },
|
||||
)
|
||||
loginDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onNavigateToMasterPasswordHint = { emailAddress ->
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.welcome
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.composableWithStayTransitions
|
||||
|
||||
private const val WELCOME_ROUTE: String = "welcome"
|
||||
|
||||
/**
|
||||
* Navigate to the welcome screen.
|
||||
*/
|
||||
fun NavController.navigateToWelcome(navOptions: NavOptions? = null) {
|
||||
this.navigate(WELCOME_ROUTE, navOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Welcome screen to the nav graph.
|
||||
*/
|
||||
fun NavGraphBuilder.welcomeDestination(
|
||||
onNavigateToCreateAccount: () -> Unit,
|
||||
onNavigateToLogin: () -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = WELCOME_ROUTE,
|
||||
) {
|
||||
WelcomeScreen(
|
||||
onNavigateToCreateAccount = onNavigateToCreateAccount,
|
||||
onNavigateToLogin = onNavigateToLogin,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.welcome
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
|
||||
/**
|
||||
* Top level composable for the welcome screen.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun WelcomeScreen(
|
||||
onNavigateToCreateAccount: () -> Unit,
|
||||
onNavigateToLogin: () -> Unit,
|
||||
viewModel: WelcomeViewModel = hiltViewModel(),
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val pagerState = rememberPagerState(pageCount = { state.pages.size })
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is WelcomeEvent.UpdatePager -> {
|
||||
pagerState.animateScrollToPage(event.index)
|
||||
}
|
||||
|
||||
WelcomeEvent.NavigateToCreateAccount -> onNavigateToCreateAccount()
|
||||
WelcomeEvent.NavigateToLogin -> onNavigateToLogin()
|
||||
}
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) { innerPadding ->
|
||||
WelcomeScreenContent(
|
||||
state = state,
|
||||
pagerState = pagerState,
|
||||
onPagerSwipe = remember(viewModel) {
|
||||
{ viewModel.trySendAction(WelcomeAction.PagerSwipe(it)) }
|
||||
},
|
||||
onDotClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(WelcomeAction.DotClick(it)) }
|
||||
},
|
||||
onCreateAccountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(WelcomeAction.CreateAccountClick) }
|
||||
},
|
||||
onLoginClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(WelcomeAction.LoginClick) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun WelcomeScreenContent(
|
||||
state: WelcomeState,
|
||||
pagerState: PagerState,
|
||||
onPagerSwipe: (Int) -> Unit,
|
||||
onDotClick: (Int) -> Unit,
|
||||
onCreateAccountClick: () -> Unit,
|
||||
onLoginClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isLandscape = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
val horizontalPadding = if (isLandscape) 128.dp else 16.dp
|
||||
|
||||
LaunchedEffect(pagerState.currentPage) {
|
||||
onPagerSwipe(pagerState.currentPage)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
HorizontalPager(state = pagerState) { index ->
|
||||
if (isLandscape) {
|
||||
WelcomeCardLandscape(
|
||||
state = state.pages[index],
|
||||
modifier = Modifier.padding(horizontal = horizontalPadding),
|
||||
)
|
||||
} else {
|
||||
WelcomeCardPortrait(
|
||||
state = state.pages[index],
|
||||
modifier = Modifier.padding(horizontal = horizontalPadding),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
IndicatorDots(
|
||||
selectedIndexProvider = { state.index },
|
||||
totalCount = state.pages.size,
|
||||
onDotClick = onDotClick,
|
||||
modifier = Modifier
|
||||
.padding(bottom = 32.dp)
|
||||
.height(44.dp),
|
||||
)
|
||||
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = R.string.create_account),
|
||||
onClick = onCreateAccountClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = horizontalPadding)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.log_in),
|
||||
onClick = onLoginClick,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = horizontalPadding)
|
||||
.padding(bottom = 32.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WelcomeCardLandscape(
|
||||
state: WelcomeState.WelcomeCard,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Image(
|
||||
painter = rememberVectorPainter(id = state.imageRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(132.dp),
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(start = 40.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = state.titleRes),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = state.messageRes),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WelcomeCardPortrait(
|
||||
state: WelcomeState.WelcomeCard,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Image(
|
||||
painter = rememberVectorPainter(id = state.imageRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(200.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = state.titleRes),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = 48.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = state.messageRes),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IndicatorDots(
|
||||
selectedIndexProvider: () -> Int,
|
||||
totalCount: Int,
|
||||
onDotClick: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
items(totalCount) { index ->
|
||||
val color = animateColorAsState(
|
||||
targetValue = if (index == selectedIndexProvider()) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
|
||||
},
|
||||
label = "dotColor",
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(color.value)
|
||||
.clickable { onDotClick(index) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.welcome
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Manages application state for the welcome screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class WelcomeViewModel @Inject constructor() :
|
||||
BaseViewModel<WelcomeState, WelcomeEvent, WelcomeAction>(
|
||||
initialState = WelcomeState(
|
||||
index = 0,
|
||||
pages = listOf(
|
||||
WelcomeState.WelcomeCard.CardOne,
|
||||
WelcomeState.WelcomeCard.CardTwo,
|
||||
WelcomeState.WelcomeCard.CardThree,
|
||||
WelcomeState.WelcomeCard.CardFour,
|
||||
),
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: WelcomeAction) {
|
||||
when (action) {
|
||||
is WelcomeAction.PagerSwipe -> handlePagerSwipe(action)
|
||||
is WelcomeAction.DotClick -> handleDotClick(action)
|
||||
WelcomeAction.CreateAccountClick -> handleCreateAccountClick()
|
||||
WelcomeAction.LoginClick -> handleLoginClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePagerSwipe(action: WelcomeAction.PagerSwipe) {
|
||||
mutableStateFlow.update { it.copy(index = action.index) }
|
||||
}
|
||||
|
||||
private fun handleDotClick(action: WelcomeAction.DotClick) {
|
||||
mutableStateFlow.update { it.copy(index = action.index) }
|
||||
sendEvent(WelcomeEvent.UpdatePager(index = action.index))
|
||||
}
|
||||
|
||||
private fun handleCreateAccountClick() {
|
||||
sendEvent(WelcomeEvent.NavigateToCreateAccount)
|
||||
}
|
||||
|
||||
private fun handleLoginClick() {
|
||||
sendEvent(WelcomeEvent.NavigateToLogin)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state of the welcome screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data class WelcomeState(
|
||||
val index: Int,
|
||||
val pages: List<WelcomeCard>,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* A sealed class to represent the different cards the user can view on the welcome screen.
|
||||
*/
|
||||
sealed class WelcomeCard : Parcelable {
|
||||
abstract val imageRes: Int
|
||||
abstract val titleRes: Int
|
||||
abstract val messageRes: Int
|
||||
|
||||
/**
|
||||
* Represents the first card the user should see on the welcome screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object CardOne : WelcomeCard() {
|
||||
override val imageRes: Int = R.drawable.welcome_1
|
||||
override val titleRes: Int = R.string.privacy_prioritized
|
||||
override val messageRes: Int = R.string.welcome_message_1
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the second card the user should see on the welcome screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object CardTwo : WelcomeCard() {
|
||||
override val imageRes: Int = R.drawable.welcome_2
|
||||
override val titleRes: Int = R.string.never_guess_again
|
||||
override val messageRes: Int = R.string.welcome_message_2
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the third card the user should see on the welcome screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object CardThree : WelcomeCard() {
|
||||
override val imageRes: Int = R.drawable.welcome_3
|
||||
override val titleRes: Int = R.string.level_up_your_logins
|
||||
override val messageRes: Int = R.string.welcome_message_3
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the fourth card the user should see on the welcome screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data object CardFour : WelcomeCard() {
|
||||
override val imageRes: Int = R.drawable.welcome_4
|
||||
override val titleRes: Int = R.string.your_data_when_and_where_you_need_it
|
||||
override val messageRes: Int = R.string.welcome_message_4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models events for the welcome screen.
|
||||
*/
|
||||
sealed class WelcomeEvent {
|
||||
/**
|
||||
* Updates the current index of the pager.
|
||||
*/
|
||||
data class UpdatePager(
|
||||
val index: Int,
|
||||
) : WelcomeEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the create account screen.
|
||||
*/
|
||||
data object NavigateToCreateAccount : WelcomeEvent()
|
||||
|
||||
/**
|
||||
* Navigates to the login screen.
|
||||
*/
|
||||
data object NavigateToLogin : WelcomeEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the welcome screen.
|
||||
*/
|
||||
sealed class WelcomeAction {
|
||||
/**
|
||||
* Swipe the pager to the given [index].
|
||||
*/
|
||||
data class PagerSwipe(
|
||||
val index: Int,
|
||||
) : WelcomeAction()
|
||||
|
||||
/**
|
||||
* Click one of the page indicator dots at the given [index].
|
||||
*/
|
||||
data class DotClick(
|
||||
val index: Int,
|
||||
) : WelcomeAction()
|
||||
|
||||
/**
|
||||
* Click the create account button.
|
||||
*/
|
||||
data object CreateAccountClick : WelcomeAction()
|
||||
|
||||
/**
|
||||
* Click the login button.
|
||||
*/
|
||||
data object LoginClick : WelcomeAction()
|
||||
}
|
|
@ -29,6 +29,7 @@ import com.x8bit.bitwarden.ui.auth.feature.trusteddevice.trustedDeviceGraph
|
|||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
|
||||
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
|
||||
import com.x8bit.bitwarden.ui.auth.feature.welcome.navigateToWelcome
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.util.toVaultItemListingType
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval.navigateToLoginApproval
|
||||
import com.x8bit.bitwarden.ui.platform.feature.splash.SPLASH_ROUTE
|
||||
|
@ -85,6 +86,7 @@ fun RootNavScreen(
|
|||
|
||||
val targetRoute = when (state) {
|
||||
RootNavState.Auth -> AUTH_GRAPH_ROUTE
|
||||
RootNavState.AuthWithWelcome -> AUTH_GRAPH_ROUTE
|
||||
RootNavState.ResetPassword -> RESET_PASSWORD_ROUTE
|
||||
RootNavState.SetPassword -> SET_PASSWORD_ROUTE
|
||||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
|
@ -133,6 +135,7 @@ fun RootNavScreen(
|
|||
LaunchedEffect(state) {
|
||||
when (val currentState = state) {
|
||||
RootNavState.Auth -> navController.navigateToAuthGraph(rootNavOptions)
|
||||
RootNavState.AuthWithWelcome -> navController.navigateToWelcome(rootNavOptions)
|
||||
RootNavState.ResetPassword -> navController.navigateToResetPasswordGraph(rootNavOptions)
|
||||
RootNavState.SetPassword -> navController.navigateToSetPassword(rootNavOptions)
|
||||
RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
|
||||
|
|
|
@ -25,7 +25,7 @@ import javax.inject.Inject
|
|||
*/
|
||||
@HiltViewModel
|
||||
class RootNavViewModel @Inject constructor(
|
||||
authRepository: AuthRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
) : BaseViewModel<RootNavState, Unit, RootNavAction>(
|
||||
initialState = RootNavState.Splash,
|
||||
|
@ -52,7 +52,7 @@ class RootNavViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "MaxLineLength")
|
||||
@Suppress("CyclomaticComplexMethod", "MaxLineLength", "LongMethod")
|
||||
private fun handleUserStateUpdateReceive(
|
||||
action: RootNavAction.Internal.UserStateUpdateReceive,
|
||||
) {
|
||||
|
@ -68,7 +68,13 @@ class RootNavViewModel @Inject constructor(
|
|||
|
||||
userState == null ||
|
||||
!userState.activeAccount.isLoggedIn ||
|
||||
userState.hasPendingAccountAddition -> RootNavState.Auth
|
||||
userState.hasPendingAccountAddition -> {
|
||||
if (authRepository.showWelcomeCarousel) {
|
||||
RootNavState.AuthWithWelcome
|
||||
} else {
|
||||
RootNavState.Auth
|
||||
}
|
||||
}
|
||||
|
||||
userState.activeAccount.isVaultUnlocked -> {
|
||||
when (val specialCircumstance = action.specialCircumstance) {
|
||||
|
@ -135,6 +141,12 @@ sealed class RootNavState : Parcelable {
|
|||
@Parcelize
|
||||
data object Auth : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show auth nav graph starting with the welcome carousel.
|
||||
*/
|
||||
@Parcelize
|
||||
data object AuthWithWelcome : RootNavState()
|
||||
|
||||
/**
|
||||
* App should show reset password graph.
|
||||
*/
|
||||
|
|
73
app/src/main/res/drawable/welcome_1.xml
Normal file
73
app/src/main/res/drawable/welcome_1.xml
Normal file
|
@ -0,0 +1,73 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="200">
|
||||
<path
|
||||
android:pathData="M118.39,69.32H79.67C74.33,69.32 70,73.65 70,78.99V106.32C70,111.66 74.33,115.99 79.67,115.99H118.39C123.73,115.99 128.06,111.66 128.06,106.32V78.99C128.06,73.65 123.73,69.32 118.39,69.32ZM79.67,65.32C72.12,65.32 66,71.44 66,78.99V106.32C66,113.87 72.12,119.99 79.67,119.99H118.39C125.94,119.99 132.06,113.87 132.06,106.32V78.99C132.06,71.44 125.94,65.32 118.39,65.32H79.67Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M114,65V61C114,52.72 107.29,46 99,46C90.72,46 84,52.72 84,61V65H114ZM118,69H80V61C80,50.51 88.51,42 99,42C109.49,42 118,50.51 118,61V69Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M99,89.93C100.11,89.93 101,90.83 101,91.93L101,99.93C101,101.03 100.11,101.93 99,101.93C97.9,101.93 97,101.03 97,99.93L97,91.93C97,90.83 97.9,89.93 99,89.93Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M99,93.4C96.79,93.4 95,91.61 95,89.4V89.4C95,87.19 96.79,85.4 99,85.4C101.21,85.4 103,87.19 103,89.4V89.4C103,91.61 101.21,93.4 99,93.4Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M121.3,173C122.21,172.38 123.46,172.62 124.08,173.53C124.89,174.74 125.39,176.07 125.39,177.46C125.39,178.86 124.88,180.16 123.91,181.21C123.04,182.21 121.73,182.84 120.47,183.07C119.94,185.14 118.71,186.41 117.56,187.03C116.95,187.42 116.28,187.68 115.59,187.81C115.35,188.42 115.01,189 114.57,189.52C113.52,191.11 111.71,191.79 110.18,191.82C109.47,192.75 108.46,193.42 107.35,193.88C107.26,193.92 107.17,193.95 107.07,193.97C106.43,194.13 105.92,194.13 105.53,194.13L105.49,194.13C104.09,194.13 102.65,193.63 101.5,193.1C100.3,192.54 99.19,191.86 98.36,191.32C97.44,190.71 97.19,189.47 97.8,188.55C98.41,187.63 99.65,187.38 100.57,187.99C101.3,188.47 102.23,189.03 103.17,189.46C104.15,189.91 104.96,190.13 105.49,190.13C105.7,190.13 105.8,190.13 105.89,190.13C105.92,190.12 105.95,190.12 105.98,190.12C106.85,189.72 107.1,189.31 107.17,189.08C107.33,188.54 107.71,188.09 108.22,187.85C108.73,187.6 109.32,187.59 109.84,187.8C109.86,187.81 109.88,187.82 109.9,187.82C109.9,187.82 109.91,187.82 109.91,187.82C109.95,187.82 109.99,187.82 110.07,187.82C110.65,187.82 111.09,187.54 111.24,187.29C111.31,187.18 111.39,187.07 111.48,186.97C111.8,186.62 111.95,186.23 111.96,185.79C111.98,185.21 112.25,184.67 112.7,184.31C113.15,183.94 113.73,183.79 114.3,183.88C114.35,183.89 114.39,183.9 114.44,183.9C114.48,183.9 114.51,183.91 114.55,183.91C114.87,183.91 115.18,183.82 115.43,183.64C115.5,183.6 115.57,183.55 115.65,183.52C115.84,183.42 116.68,182.87 116.73,181.07C116.75,180.49 117.02,179.95 117.47,179.58C117.92,179.22 118.5,179.06 119.07,179.16C119.15,179.18 119.24,179.18 119.34,179.18C119.96,179.18 120.66,178.86 120.91,178.57C120.92,178.56 120.92,178.56 120.93,178.55C120.94,178.54 120.94,178.53 120.95,178.52C121.27,178.18 121.39,177.83 121.39,177.46C121.39,177.05 121.25,176.49 120.77,175.78C120.15,174.87 120.39,173.62 121.3,173Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M95.47,149.98C93.74,151.13 91.78,153.18 89.9,156.83L89.89,156.85L89.88,156.88C89.67,157.26 89.64,157.81 89.96,158.41C90.27,158.91 91.25,159.48 92.89,158.63L92.89,158.63C94.91,157.58 96.42,156.48 97.28,155.85C97.42,155.75 97.54,155.66 97.65,155.58L98.17,155.21H98.81C100.21,155.21 101,155.12 101.47,155.04L103.03,154.78L103.64,156.24C104.01,157.1 104.64,158.5 106.01,159.92C107.37,161.33 109.54,162.84 113.08,163.75L113.58,163.88L122.96,172.74L135.57,165.65L126.23,149.61L115.19,154.62L102.52,149.03C102.49,149.02 102.46,149.01 102.44,149C102.43,149 102.36,148.98 102.31,148.96C102.28,148.95 102.26,148.94 102.25,148.93C102.12,148.89 101.96,148.83 101.75,148.78C101.34,148.66 100.74,148.55 100,148.55C98.76,148.55 97.18,148.85 95.47,149.98ZM93.26,146.65C95.69,145.04 98.06,144.55 100,144.55C101.17,144.55 102.12,144.73 102.8,144.92C103.1,145 103.35,145.08 103.53,145.15H103.62L115.17,150.24L125.32,145.63C126.15,145.24 127.02,145.35 127.6,145.55C128.38,145.81 128.98,146.34 129.32,146.97L139.65,164.71L139.71,164.84C140.3,166.33 139.64,167.74 138.65,168.48L138.55,168.56L124.21,176.62L124.17,176.65C123.93,176.76 123.67,176.86 123.35,176.92C123.1,176.96 122.87,176.96 122.79,176.96C122.78,176.96 122.78,176.96 122.77,176.96C122.09,176.96 121.28,176.68 120.69,176.1L111.58,167.49C107.62,166.38 104.94,164.58 103.13,162.7C101.91,161.43 101.12,160.17 100.59,159.16C100.26,159.18 99.89,159.2 99.47,159.21C98.56,159.87 96.89,161.06 94.74,162.18C91.81,163.7 88.2,163.31 86.5,160.42L86.48,160.39L86.47,160.37C85.61,158.79 85.38,156.78 86.35,154.98C88.47,150.88 90.84,148.25 93.26,146.65Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M76.54,175.07C77.74,173.63 79.32,172.48 81.46,172.65L81.47,172.65L81.48,172.65C82.74,172.78 83.77,173.33 84.53,173.93C84.99,173.74 85.46,173.59 85.93,173.48C86.77,173.3 87.74,173.25 88.64,173.55L88.66,173.56L88.67,173.56C89.99,174.02 90.88,174.95 91.48,175.84C91.62,175.84 91.76,175.85 91.91,175.86C94.43,176.1 95.89,177.52 96.69,178.74C98.02,179.07 99,179.82 99.67,180.74C100.43,181.78 100.73,182.95 100.88,183.59C101.36,185.4 101.56,188.43 100.89,191.34L100.87,191.41L100.85,191.48C100.15,193.76 98.07,195 95.83,195C94.1,195 92.28,194.36 90.91,193.16C88.54,192.93 86.52,192.12 85.17,190.48C83.82,190.44 82.36,190 81.04,188.95L81,188.91L80.96,188.88C80.33,188.31 79.67,187.51 79.21,186.4C78.68,186.18 78.07,185.88 77.48,185.48C76.45,184.79 75.32,183.71 74.78,182.1C73.99,179.73 74.76,177.33 76.51,175.11L76.53,175.09L76.54,175.07ZM79.63,177.61C78.39,179.2 78.36,180.2 78.57,180.83C78.74,181.32 79.12,181.77 79.71,182.16C80.29,182.55 80.94,182.8 81.37,182.92L82.49,183.25L82.76,184.38C82.93,185.13 83.28,185.57 83.58,185.85C84.19,186.32 84.82,186.49 85.4,186.49C85.54,186.49 85.66,186.47 85.76,186.45L87.29,186.11L87.98,187.51C88.02,187.58 88.06,187.65 88.1,187.72C88.52,188.36 89.34,188.96 91.13,189.17L92.26,188.63L93.25,189.85C93.8,190.52 94.83,191 95.83,191C96.57,191 96.89,190.67 97.01,190.36C97.51,188.1 97.32,185.77 97.01,184.59L97,184.56L96.99,184.52C96.86,183.95 96.7,183.46 96.44,183.11C96.26,182.85 95.98,182.6 95.29,182.56L94.05,182.49L93.56,181.35C93.31,180.75 92.76,179.96 91.53,179.85C91.33,179.83 91.15,179.85 90.98,179.91L89.23,180.46L88.53,178.77C88.39,178.43 88.2,178.09 87.97,177.82C87.76,177.56 87.55,177.41 87.37,177.35C87.34,177.34 87.16,177.31 86.78,177.39C86.4,177.47 85.94,177.65 85.47,177.94C85.42,177.97 85.38,178 85.34,178.03L83.83,179.08L82.66,177.67C82.44,177.41 82.18,177.15 81.89,176.95C81.61,176.76 81.35,176.66 81.11,176.64C80.84,176.62 80.42,176.68 79.63,177.61Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M86.12,175.89C86.7,176.83 86.41,178.06 85.47,178.64C84.93,178.97 83.68,179.84 83.35,181.2C83.13,182.12 83.09,182.84 83.12,183.34C83.15,183.8 83.22,184.01 83.22,184.01C83.22,184.01 83.21,184.01 83.21,183.99C83.69,184.99 83.28,186.18 82.28,186.66C81.29,187.15 80.09,186.73 79.61,185.74C79.12,184.74 78.86,182.76 79.46,180.26C80.21,177.21 82.78,175.6 83.36,175.24C84.31,174.66 85.54,174.95 86.12,175.89Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M92.14,177.35C92.64,178.33 92.26,179.53 91.28,180.04C90.88,180.24 89.4,181.38 88.76,183.56C88.11,185.8 88.52,187.03 88.57,187.15C88.57,187.16 88.57,187.16 88.57,187.16C89.13,188.12 88.8,189.34 87.85,189.9C86.9,190.45 85.67,190.13 85.11,189.18C84.43,188 84.01,185.56 84.92,182.44C85.89,179.1 88.17,177.14 89.45,176.48C90.43,175.98 91.63,176.36 92.14,177.35Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M96.43,180.65C96.94,181.63 96.55,182.84 95.57,183.34C95.57,183.34 95.57,183.34 95.56,183.35C95.52,183.39 95.28,183.58 94.96,184.07C94.62,184.58 94.28,185.28 94.05,186.09C93.83,186.82 93.76,187.7 93.78,188.47C93.8,188.85 93.83,189.16 93.87,189.39C93.89,189.5 93.91,189.56 93.92,189.59C94.38,190.52 94.05,191.68 93.13,192.21C92.18,192.77 90.96,192.44 90.4,191.49C90.13,191.03 90,190.48 89.94,190.1C89.85,189.65 89.8,189.14 89.78,188.6C89.75,187.53 89.84,186.21 90.21,184.97C90.55,183.79 91.06,182.72 91.62,181.86C92.15,181.06 92.87,180.23 93.74,179.79C94.72,179.28 95.93,179.67 96.43,180.65Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M102.95,182.08C102.95,182.08 102.95,182.08 102.95,182.08L102.96,182.09L103,182.12C103.03,182.15 103.08,182.2 103.14,182.25C103.27,182.37 103.46,182.54 103.69,182.74C104.15,183.16 104.79,183.72 105.52,184.32C107.01,185.57 108.67,186.83 109.75,187.37C110.74,187.87 111.14,189.07 110.65,190.05C110.15,191.04 108.95,191.44 107.97,190.95C106.41,190.17 104.43,188.62 102.96,187.39C102.19,186.76 101.51,186.16 101.03,185.73C100.78,185.51 100.59,185.33 100.45,185.21C100.38,185.15 100.33,185.1 100.29,185.06L100.25,185.02L100.23,185.01C100.23,185.01 100.23,185.01 101.59,183.54L100.23,185.01C99.42,184.26 99.38,182.99 100.13,182.18C100.88,181.37 102.14,181.33 102.95,182.08Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M102.07,176.94C102.81,176.11 104.07,176.03 104.9,176.76C104.9,176.76 104.9,176.76 104.9,176.76L104.91,176.77L104.96,176.82C105.01,176.85 105.07,176.91 105.16,176.99C105.34,177.14 105.59,177.36 105.91,177.63C106.54,178.16 107.43,178.9 108.42,179.68C110.46,181.29 112.77,182.97 114.34,183.72C115.34,184.19 115.76,185.39 115.29,186.38C114.82,187.38 113.63,187.81 112.63,187.33C110.6,186.37 107.95,184.41 105.94,182.82C104.9,182 103.98,181.24 103.32,180.68C103,180.4 102.73,180.17 102.54,180.01C102.45,179.93 102.38,179.87 102.33,179.83L102.27,179.78L102.25,179.76C102.25,179.76 102.25,179.76 103.57,178.26L102.25,179.76C101.42,179.03 101.34,177.76 102.07,176.94Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M106.01,171.68C106.73,170.84 107.99,170.73 108.83,171.45C108.83,171.45 108.83,171.45 108.83,171.45L108.85,171.46L108.91,171.51C108.96,171.55 109.04,171.62 109.13,171.7C109.33,171.87 109.62,172.11 109.98,172.4C110.7,172.99 111.71,173.79 112.83,174.65C115.14,176.4 117.79,178.25 119.6,179.08C120.61,179.54 121.05,180.73 120.59,181.73C120.13,182.74 118.94,183.18 117.94,182.72C115.67,181.68 112.7,179.57 110.41,177.83C109.24,176.94 108.2,176.11 107.46,175.5C107.08,175.2 106.78,174.95 106.57,174.77C106.47,174.69 106.39,174.62 106.33,174.57L106.27,174.52L106.25,174.5C106.25,174.5 106.25,174.5 107.54,172.97L106.25,174.5C105.4,173.79 105.3,172.52 106.01,171.68Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M59,148h34.46v31.9h-34.46z"/>
|
||||
<path
|
||||
android:pathData="M72.97,152.33C75.4,153.57 77.48,154.21 78.98,154.68H79C80.05,155.01 80.6,155.19 80.94,155.37C81.43,155.63 82,155.68 82.52,155.5C82.76,155.42 82.97,155.32 83.09,155.26C83.09,155.26 83.1,155.26 83.11,155.26L93.46,150.03C93.31,148.94 92.31,148.17 91.21,148.31C87.87,148.76 84.19,150.4 82.18,151.33L80.18,150.87C78.52,150.36 76.44,149.71 73.98,148.35C73.96,148.34 73.94,148.33 73.91,148.32C72.65,147.69 70.88,148.01 70,149.48L59.44,167.56C58.68,168.84 58.84,170.73 60.43,171.69C60.47,171.71 60.5,171.73 60.54,171.75L75.78,179.67C76.76,180.18 77.97,179.8 78.48,178.82C78.99,177.84 78.61,176.63 77.63,176.12L63.39,168.73L72.97,152.33Z"
|
||||
android:fillColor="#020F66"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M180.1,64.02C185.31,81.07 184.91,99.34 178.94,116.14C172.98,132.94 161.76,147.39 146.95,157.37C146.03,157.99 144.79,157.75 144.17,156.83C143.55,155.92 143.8,154.67 144.71,154.06C158.82,144.55 169.49,130.79 175.17,114.8C180.85,98.81 181.24,81.42 176.27,65.19C171.31,48.97 161.25,34.75 147.58,24.64C133.91,14.52 117.34,9.04 100.32,9C83.29,8.96 66.7,14.36 52.98,24.41C39.26,34.45 29.13,48.62 24.09,64.82C19.04,81.03 19.34,98.41 24.94,114.43C30.54,130.46 41.15,144.27 55.21,153.84C56.12,154.46 56.36,155.7 55.74,156.62C55.12,157.53 53.87,157.77 52.96,157.14C38.19,147.09 27.05,132.59 21.17,115.75C15.28,98.92 14.97,80.66 20.27,63.63C25.57,46.61 36.2,31.73 50.61,21.18C65.02,10.63 82.45,4.96 100.33,5C118.21,5.04 135.6,10.8 149.96,21.42C164.32,32.04 174.88,46.97 180.1,64.02Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M172.33,66.32C177.03,81.68 176.66,98.14 171.29,113.28C165.91,128.42 155.8,141.44 142.45,150.43C141.99,150.74 141.37,150.62 141.06,150.16C140.75,149.7 140.87,149.08 141.33,148.77C154.33,140.02 164.17,127.34 169.4,112.61C174.63,97.87 174.99,81.85 170.41,66.9C165.84,51.95 156.57,38.85 143.98,29.54C131.38,20.22 116.11,15.17 100.43,15.13C84.74,15.09 69.45,20.07 56.81,29.32C44.16,38.58 34.83,51.63 30.18,66.56C25.53,81.49 25.81,97.51 30.97,112.27C36.13,127.03 45.91,139.76 58.86,148.57C59.32,148.88 59.44,149.51 59.13,149.96C58.82,150.42 58.19,150.54 57.74,150.23C44.43,141.17 34.39,128.1 29.08,112.93C23.78,97.77 23.5,81.3 28.27,65.97C33.05,50.63 42.64,37.22 55.62,27.71C68.61,18.2 84.31,13.09 100.43,13.13C116.54,13.17 132.22,18.36 145.16,27.93C158.1,37.5 167.62,50.96 172.33,66.32Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
58
app/src/main/res/drawable/welcome_2.xml
Normal file
58
app/src/main/res/drawable/welcome_2.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="200">
|
||||
<path
|
||||
android:pathData="M21,16.62C21,9.65 26.65,4 33.62,4H93.31C100.29,4 105.94,9.65 105.94,16.62V41.26C105.94,42.36 105.04,43.26 103.94,43.26C102.83,43.26 101.94,42.36 101.94,41.26V16.62C101.94,11.86 98.08,8 93.31,8H33.62C28.86,8 25,11.86 25,16.62V146.52C25,151.29 28.86,155.14 33.62,155.14H93.59C94.7,155.14 95.59,156.04 95.59,157.14C95.59,158.25 94.7,159.14 93.59,159.14H33.62C26.65,159.14 21,153.49 21,146.52V16.62Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M59.42,22.21C59.42,21.11 60.31,20.21 61.42,20.21H65.51C66.61,20.21 67.51,21.11 67.51,22.21C67.51,23.32 66.61,24.21 65.51,24.21H61.42C60.31,24.21 59.42,23.32 59.42,22.21Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M105.7,44.86C100.94,44.86 97.08,48.71 97.08,53.48V183.38C97.08,188.14 100.94,192 105.7,192H165.38C170.14,192 174,188.14 174,183.38V53.48C174,48.71 170.14,44.86 165.38,44.86H105.7ZM93.08,53.48C93.08,46.5 98.73,40.86 105.7,40.86H165.38C172.35,40.86 178,46.5 178,53.48V183.38C178,190.35 172.35,196 165.38,196H105.7C98.73,196 93.08,190.35 93.08,183.38V53.48Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M130.14,59.07C130.14,57.97 131.04,57.07 132.14,57.07H136.41C137.51,57.07 138.41,57.97 138.41,59.07C138.41,60.18 137.51,61.07 136.41,61.07H132.14C131.04,61.07 130.14,60.18 130.14,59.07Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M77.46,58.89C77.46,57.79 78.35,56.89 79.46,56.89H83.24C85.55,56.89 87.6,58.71 87.6,61.14V64.73C87.6,65.84 86.7,66.73 85.6,66.73C84.49,66.73 83.6,65.84 83.6,64.73V61.14C83.6,61.11 83.59,61.06 83.52,61C83.46,60.94 83.36,60.89 83.24,60.89H79.46C78.35,60.89 77.46,60 77.46,58.89ZM36.42,61.15C36.42,58.71 38.45,56.91 40.77,56.91H44.55C45.66,56.91 46.55,57.8 46.55,58.91C46.55,60.01 45.66,60.91 44.55,60.91H40.77C40.63,60.91 40.54,60.96 40.48,61.01C40.43,61.06 40.42,61.11 40.42,61.15V64.75C40.42,65.85 39.52,66.75 38.42,66.75C37.31,66.75 36.42,65.85 36.42,64.75V61.15Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M38.42,103.91C39.52,103.91 40.42,104.8 40.42,105.91V109.49C40.42,109.51 40.43,109.57 40.49,109.63C40.55,109.69 40.65,109.73 40.77,109.73H44.55C45.66,109.73 46.55,110.63 46.55,111.73C46.55,112.84 45.66,113.73 44.55,113.73H40.77C38.46,113.73 36.42,111.91 36.42,109.49V105.91C36.42,104.8 37.31,103.91 38.42,103.91ZM85.6,103.91C86.7,103.91 87.6,104.8 87.6,105.91V109.49C87.6,111.9 85.57,113.73 83.24,113.73H79.46C78.35,113.73 77.46,112.84 77.46,111.73C77.46,110.63 78.35,109.73 79.46,109.73H83.24C83.37,109.73 83.47,109.68 83.53,109.63C83.58,109.57 83.6,109.52 83.6,109.49V105.91C83.6,104.8 84.49,103.91 85.6,103.91Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M62.15,70.01C57.23,70.01 53.38,72.96 53.38,76.96V79.51C53.38,79.51 53.38,79.52 53.38,79.52C53.38,79.62 53.39,80.05 53.18,80.51C53.08,80.75 52.95,80.93 52.87,81.02C52.82,81.08 52.77,81.15 52.74,81.18C52.71,81.21 52.68,81.24 52.65,81.28C52.64,81.3 52.62,81.32 52.61,81.33C52.55,81.4 52.48,81.48 52.4,81.58C52.37,81.62 52.36,81.64 52.35,81.66C52.34,81.67 52.33,81.7 52.33,81.76C52.33,81.78 52.32,81.8 52.32,81.82C52.31,81.94 52.32,82.21 52.53,82.79C53.3,84.95 54.78,87.61 56.59,89.73C58.44,91.88 60.41,93.21 62.15,93.21C63,93.21 63.97,92.89 64.97,92.26L64.97,92.25C65.98,91.62 66.98,90.72 67.93,89.64C69.69,87.61 71.03,85.14 71.52,83.17L71.09,80.86C71.05,80.79 71.01,80.73 70.98,80.66C70.88,80.46 70.81,80.25 70.78,80.03C70.78,80.02 70.78,80 70.77,79.98C70.76,79.93 70.76,79.87 70.75,79.8C70.75,79.73 70.74,79.66 70.74,79.58V78.87C70.74,78.77 70.75,78.68 70.76,78.59L70.7,76.76C70.43,74.49 69.48,72.84 68.07,71.74C66.63,70.62 64.62,70.01 62.15,70.01ZM70.77,79.78L70.77,79.78L70.77,79.78L70.77,79.79C70.77,79.79 70.77,79.78 70.77,79.78ZM75.47,83.81C75.53,83.64 75.56,83.46 75.57,83.28C75.69,82.52 75.73,81.77 75.62,81.07C75.53,80.45 75.34,79.94 75.1,79.52L74.69,76.38C74.32,73.13 72.9,70.43 70.53,68.59L70.53,68.59C68.2,66.77 65.24,66.01 62.15,66.01C56.06,66.01 49.38,69.84 49.38,76.96V78.96C49.35,78.99 49.32,79.02 49.3,79.05L49.3,79.06C48.85,79.61 48.43,80.39 48.34,81.42C48.24,82.39 48.47,83.31 48.76,84.13L48.76,84.13C49.69,86.75 51.42,89.84 53.55,92.33L53.55,92.33C55.56,94.67 58.56,97.21 62.15,97.21C63.98,97.21 65.68,96.54 67.1,95.64C68.53,94.75 69.82,93.54 70.94,92.27L70.94,92.27C73.08,89.81 74.86,86.65 75.47,83.81ZM49.38,79.52C49.38,79.52 49.38,79.52 49.38,79.52L49.38,79.52Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M56.68,91.31C57.92,91.17 59.05,92.07 59.18,93.32C59.29,94.28 59.33,95.8 59.14,97.39C58.96,98.93 58.52,100.84 57.44,102.34C56.71,103.36 55.29,103.59 54.28,102.86C53.26,102.13 53.02,100.71 53.76,99.69C54.16,99.12 54.48,98.12 54.63,96.85C54.78,95.64 54.74,94.46 54.67,93.81C54.53,92.56 55.43,91.44 56.68,91.31Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M68.12,91.31C69.37,91.44 70.27,92.56 70.13,93.81C70.06,94.46 70.02,95.64 70.17,96.85C70.32,98.12 70.64,99.12 71.04,99.69C71.78,100.71 71.54,102.13 70.52,102.86C69.51,103.59 68.09,103.36 67.36,102.34C66.28,100.84 65.84,98.93 65.66,97.39C65.47,95.8 65.51,94.28 65.62,93.32C65.75,92.07 66.88,91.17 68.12,91.31Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M104.11,118.55C104.11,110.43 110.72,103.86 118.83,103.86H152.25C160.39,103.86 166.98,110.45 166.98,118.55C166.98,126.68 160.37,133.25 152.25,133.25H118.83C110.7,133.25 104.11,126.66 104.11,118.55ZM118.83,107.86C112.91,107.86 108.11,112.65 108.11,118.55C108.11,124.44 112.9,129.25 118.83,129.25H152.25C158.18,129.25 162.98,124.46 162.98,118.55C162.98,112.66 158.19,107.86 152.25,107.86H118.83Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M119.37,122.22C121.4,122.22 123.04,120.58 123.04,118.55C123.04,116.53 121.4,114.89 119.37,114.89C117.33,114.89 115.69,116.53 115.69,118.55C115.69,120.58 117.33,122.22 119.37,122.22Z"
|
||||
android:fillColor="#020F66"/>
|
||||
<path
|
||||
android:pathData="M129.66,122.22C131.7,122.22 133.34,120.58 133.34,118.55C133.34,116.53 131.7,114.89 129.66,114.89C127.63,114.89 125.99,116.53 125.99,118.55C125.99,120.58 127.63,122.22 129.66,122.22Z"
|
||||
android:fillColor="#020F66"/>
|
||||
<path
|
||||
android:pathData="M139.98,122.22C142.01,122.22 143.66,120.58 143.66,118.55C143.66,116.53 142.01,114.89 139.98,114.89C137.95,114.89 136.3,116.53 136.3,118.55C136.3,120.58 137.95,122.22 139.98,122.22Z"
|
||||
android:fillColor="#020F66"/>
|
||||
<path
|
||||
android:pathData="M150.27,122.22C152.3,122.22 153.95,120.58 153.95,118.55C153.95,116.53 152.3,114.89 150.27,114.89C148.24,114.89 146.59,116.53 146.59,118.55C146.59,120.58 148.24,122.22 150.27,122.22Z"
|
||||
android:fillColor="#020F66"/>
|
||||
</vector>
|
46
app/src/main/res/drawable/welcome_3.xml
Normal file
46
app/src/main/res/drawable/welcome_3.xml
Normal file
|
@ -0,0 +1,46 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="200">
|
||||
<path
|
||||
android:pathData="M3.02,48.35C3.02,41.17 8.84,35.35 16.02,35.35H146.45C153.63,35.35 159.45,41.17 159.45,48.35V61.99H155.45V48.35C155.45,43.38 151.43,39.35 146.45,39.35H16.02C11.05,39.35 7.02,43.38 7.02,48.35V126.44C7.02,131.41 11.05,135.44 16.02,135.44H28.96V139.44H16.02C8.84,139.44 3.02,133.62 3.02,126.44V48.35ZM84.43,135.44H104.21V139.44H84.43V135.44Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M144.95,113.02C159.1,113.02 170.57,101.54 170.57,87.4C170.57,73.25 159.1,61.78 144.95,61.78C130.8,61.78 119.33,73.25 119.33,87.4C119.33,101.54 130.8,113.02 144.95,113.02ZM144.95,117.02C161.31,117.02 174.57,103.75 174.57,87.4C174.57,71.04 161.31,57.78 144.95,57.78C128.59,57.78 115.33,71.04 115.33,87.4C115.33,103.75 128.59,117.02 144.95,117.02Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M57.11,90.42C48.92,90.42 41.75,98.35 41.75,108.85H37.75C37.75,96.78 46.13,86.42 57.11,86.42C68.1,86.42 76.48,96.78 76.48,108.85H72.48C72.48,98.35 65.31,90.42 57.11,90.42Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M100.39,157.96H187.48C189.57,157.96 190.38,157.41 190.62,157.16C190.76,157.02 191.18,156.51 190.92,154.91C187.44,133.48 168.25,116.93 144.93,116.93C121.61,116.93 102.43,133.48 98.95,154.91C98.81,155.74 98.99,156.7 99.36,157.33C99.53,157.61 99.69,157.75 99.81,157.82C99.9,157.88 100.06,157.96 100.39,157.96ZM100.39,161.96H187.48C192.85,161.96 195.67,159.19 194.87,154.27C191.06,130.84 170.15,112.93 144.93,112.93C119.71,112.93 98.81,130.84 95,154.27C94.46,157.6 96.19,161.96 100.39,161.96Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M74.73,110.8H39.49C35.07,110.8 31.49,114.38 31.49,118.8V143.83C31.49,148.25 35.07,151.83 39.49,151.83H74.73C79.15,151.83 82.73,148.25 82.73,143.83V118.8C82.73,114.38 79.15,110.8 74.73,110.8ZM39.49,106.8C32.86,106.8 27.49,112.18 27.49,118.8V143.83C27.49,150.46 32.86,155.83 39.49,155.83H74.73C81.36,155.83 86.73,150.46 86.73,143.83V118.8C86.73,112.18 81.36,106.8 74.73,106.8H39.49Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M57.11,131.36C58.22,131.36 59.11,132.25 59.11,133.36L59.11,140.53C59.11,141.64 58.22,142.53 57.11,142.53C56.01,142.53 55.11,141.64 55.11,140.53L55.11,133.36C55.11,132.25 56.01,131.36 57.11,131.36Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M156.18,52.49H4V50.49H156.18V52.49Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M61.6,129.82C61.6,132.33 59.57,134.36 57.06,134.36C54.54,134.36 52.51,132.33 52.51,129.82C52.51,127.3 54.54,125.27 57.06,125.27C59.57,125.27 61.6,127.3 61.6,129.82Z"
|
||||
android:fillColor="#10949D"/>
|
||||
<path
|
||||
android:pathData="M131,45C131,46.1 130.1,47 129,47C127.89,47 127,46.1 127,45C127,43.9 127.89,43 129,43C130.1,43 131,43.9 131,45Z"
|
||||
android:fillColor="#020F66"/>
|
||||
<path
|
||||
android:pathData="M140,45C140,46.1 139.1,47 138,47C136.9,47 136,46.1 136,45C136,43.9 136.9,43 138,43C139.1,43 140,43.9 140,45Z"
|
||||
android:fillColor="#020F66"/>
|
||||
<path
|
||||
android:pathData="M149,45C149,46.1 148.1,47 147,47C145.9,47 145,46.1 145,45C145,43.9 145.9,43 147,43C148.1,43 149,43.9 149,45Z"
|
||||
android:fillColor="#020F66"/>
|
||||
</vector>
|
56
app/src/main/res/drawable/welcome_4.xml
Normal file
56
app/src/main/res/drawable/welcome_4.xml
Normal file
|
@ -0,0 +1,56 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="200">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h200v200h-200z"/>
|
||||
<path
|
||||
android:pathData="M28.49,54.09C28.49,50.04 31.77,46.77 35.81,46.77H155.87C159.91,46.77 163.19,50.04 163.19,54.09V59.35H161.19V54.09C161.19,51.15 158.81,48.77 155.87,48.77H35.81C32.87,48.77 30.49,51.15 30.49,54.09V69.88H28.49V54.09ZM44.23,124.7L125.33,124.7V126.7L44.23,126.7V124.7Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M62.24,153.09C62.24,151.98 63.14,151.09 64.24,151.09H125.33C126.43,151.09 127.33,151.98 127.33,153.09C127.33,154.19 126.43,155.09 125.33,155.09H64.24C63.14,155.09 62.24,154.19 62.24,153.09Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M83.04,153.09V134.38H87.04V153.09H83.04Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M104.07,153.09V134.38H108.07V153.09H104.07Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M20.12,53.03C20.12,44.95 26.67,38.39 34.76,38.39H156.93C165.01,38.39 171.56,44.95 171.56,53.03V59.35H167.56V53.03C167.56,47.16 162.8,42.39 156.93,42.39H34.76C28.88,42.39 24.12,47.16 24.12,53.03V69.88H20.12V53.03ZM43.71,131.07H125.33V135.07H43.71V131.07Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M95.84,69.83C91.6,69.83 88.41,72.77 88.41,76.06V81.47H86.41V76.06C86.41,71.39 90.8,67.83 95.84,67.83C100.88,67.83 105.27,71.37 105.27,76.06V81.47H103.27V76.06C103.27,72.75 100.08,69.83 95.84,69.83Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M82.2,87.87C82.2,83.83 85.48,80.55 89.52,80.55H102.16C106.2,80.55 109.48,83.83 109.48,87.87V96.29C109.48,100.34 106.2,103.61 102.16,103.61H89.52C85.48,103.61 82.2,100.34 82.2,96.29V87.87ZM89.52,82.55C86.58,82.55 84.2,84.93 84.2,87.87V96.29C84.2,99.23 86.58,101.61 89.52,101.61H102.16C105.1,101.61 107.48,99.23 107.48,96.29V87.87C107.48,84.93 105.1,82.55 102.16,82.55H89.52Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M95,91.54C94.52,91.28 94.21,90.8 94.21,90.25L94.21,90C94.21,89.18 94.91,88.51 95.79,88.51C96.66,88.51 97.37,89.18 97.37,90V90.25C97.37,90.8 97.05,91.28 96.58,91.54L96.58,94.5C96.58,94.91 96.22,95.25 95.79,95.25C95.35,95.25 95,94.91 95,94.5L95,91.54Z"
|
||||
android:fillColor="#10949D"/>
|
||||
<path
|
||||
android:pathData="M35.81,71.78H10.53C6.92,71.78 4,74.7 4,78.31V141.5C4,145.11 6.92,148.03 10.53,148.03H35.81C39.42,148.03 42.34,145.11 42.34,141.5V78.31C42.34,74.7 39.42,71.78 35.81,71.78ZM10.53,67.78C4.72,67.78 0,72.49 0,78.31V141.5C0,147.32 4.72,152.03 10.53,152.03H35.81C41.63,152.03 46.34,147.32 46.34,141.5V78.31C46.34,72.49 41.63,67.78 35.81,67.78H10.53Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M21.45,77.05C21.45,76.47 21.93,76 22.51,76H23.82C24.4,76 24.87,76.47 24.87,77.05C24.87,77.64 24.4,78.11 23.82,78.11H22.51C21.93,78.11 21.45,77.64 21.45,77.05Z"
|
||||
android:fillColor="#10949D"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M123.33,67.78C123.33,62.02 128,57.35 133.76,57.35H189.57C195.33,57.35 200,62.02 200,67.78V152.03C200,157.79 195.33,162.46 189.57,162.46H133.76C128,162.46 123.33,157.79 123.33,152.03V67.78ZM133.76,61.35C130.21,61.35 127.33,64.23 127.33,67.78V152.03C127.33,155.58 130.21,158.46 133.76,158.46H189.57C193.12,158.46 196,155.58 196,152.03V67.78C196,64.23 193.12,61.35 189.57,61.35H133.76Z"
|
||||
android:fillColor="#020F66"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M164.3,153.09C164.3,154.25 163.35,155.19 162.19,155.19C161.03,155.19 160.09,154.25 160.09,153.09C160.09,151.92 161.03,150.98 162.19,150.98C163.35,150.98 164.3,151.92 164.3,153.09Z"
|
||||
android:fillColor="#10949D"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -927,4 +927,12 @@ Do you want to switch to this account?</string>
|
|||
<string name="self_hosted_server_url">Self-hosted server URL</string>
|
||||
<string name="passkey_operation_failed_because_user_could_not_be_verified">Passkey operation failed because user could not be verified.</string>
|
||||
<string name="user_verification_direction">User verification</string>
|
||||
<string name="privacy_prioritized">Privacy, prioritized</string>
|
||||
<string name="welcome_message_1">Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you.</string>
|
||||
<string name="never_guess_again">Never guess again</string>
|
||||
<string name="welcome_message_2">Set up biometric unlock and autofill to log into your accounts without typing a single letter.</string>
|
||||
<string name="level_up_your_logins">Level up your logins</string>
|
||||
<string name="welcome_message_3">Use the generator to create and save strong, unique passwords for all your accounts.</string>
|
||||
<string name="your_data_when_and_where_you_need_it">Your data, when and where you need it</string>
|
||||
<string name="welcome_message_4">Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps.</string>
|
||||
</resources>
|
||||
|
|
|
@ -84,9 +84,11 @@ import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
|||
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.NotificationLogoutData
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -217,6 +219,7 @@ class AuthRepositoryTest {
|
|||
getActivePoliciesFlow(type = PolicyTypeJson.MASTER_PASSWORD)
|
||||
} returns mutableActivePolicyFlow
|
||||
}
|
||||
private val featureFlagManager: FeatureFlagManager = mockk()
|
||||
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
|
@ -236,6 +239,7 @@ class AuthRepositoryTest {
|
|||
dispatcherManager = dispatcherManager,
|
||||
pushManager = pushManager,
|
||||
policyManager = policyManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
|
@ -4504,6 +4508,22 @@ class AuthRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `showWelcomeCarousel should return value from settings repository and feature flag manager`() {
|
||||
every { settingsRepository.hasUserLoggedInOrCreatedAccount } returns false
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingCarousel) } returns true
|
||||
assertTrue(repository.showWelcomeCarousel)
|
||||
|
||||
every { settingsRepository.hasUserLoggedInOrCreatedAccount } returns true
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingCarousel) } returns true
|
||||
assertFalse(repository.showWelcomeCarousel)
|
||||
|
||||
every { settingsRepository.hasUserLoggedInOrCreatedAccount } returns true
|
||||
every { featureFlagManager.getFeatureFlag(FlagKey.OnboardingCarousel) } returns false
|
||||
assertFalse(repository.showWelcomeCarousel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOrganizationDomainSsoDetails Failure should return Failure `() = runTest {
|
||||
val email = "test@gmail.com"
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.welcome
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
class WelcomeScreenTest : BaseComposeTest() {
|
||||
private var onNavigateToCreateAccountCalled = false
|
||||
private var onNavigateToLoginCalled = false
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<WelcomeEvent>()
|
||||
private val viewModel = mockk<WelcomeViewModel>(relaxed = true) {
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
WelcomeScreen(
|
||||
onNavigateToCreateAccount = { onNavigateToCreateAccountCalled = true },
|
||||
onNavigateToLogin = { onNavigateToLoginCalled = true },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pages should display and update according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Privacy, prioritized")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableEventFlow.tryEmit(WelcomeEvent.UpdatePager(index = 1))
|
||||
composeTestRule
|
||||
.onNodeWithText("Privacy, prioritized")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText("Never guess again")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { it.copy(pages = listOf(WelcomeState.WelcomeCard.CardThree)) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Level up your logins")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Config(qualifiers = "land")
|
||||
@Test
|
||||
fun `pages should display and update according to state in landscape mode`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Privacy, prioritized")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableEventFlow.tryEmit(WelcomeEvent.UpdatePager(index = 1))
|
||||
composeTestRule
|
||||
.onNodeWithText("Privacy, prioritized")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText("Never guess again")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { it.copy(pages = listOf(WelcomeState.WelcomeCard.CardThree)) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Level up your logins")
|
||||
.assertExists()
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToCreateAccount event should call onNavigateToCreateAccount`() {
|
||||
mutableEventFlow.tryEmit(WelcomeEvent.NavigateToCreateAccount)
|
||||
assertTrue(onNavigateToCreateAccountCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToLogin event should call onNavigateToLogin`() {
|
||||
mutableEventFlow.tryEmit(WelcomeEvent.NavigateToLogin)
|
||||
assertTrue(onNavigateToLoginCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create account button click should send CreateAccountClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Create account")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(WelcomeAction.CreateAccountClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login button click should send LoginClick action`() {
|
||||
// Use an empty list of pages to guarantee that the login button
|
||||
// will be in view on the UI testing viewport.
|
||||
mutableStateFlow.update { it.copy(pages = emptyList()) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Log In")
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(WelcomeAction.LoginClick) }
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = WelcomeState(
|
||||
index = 0,
|
||||
pages = listOf(
|
||||
WelcomeState.WelcomeCard.CardOne,
|
||||
WelcomeState.WelcomeCard.CardTwo,
|
||||
),
|
||||
)
|
|
@ -0,0 +1,95 @@
|
|||
package com.x8bit.bitwarden.ui.auth.feature.welcome
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class WelcomeViewModelTest : BaseViewModelTest() {
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = WelcomeViewModel()
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PagerSwipe should update state`() = runTest {
|
||||
val viewModel = WelcomeViewModel()
|
||||
val newIndex = 2
|
||||
|
||||
viewModel.trySendAction(WelcomeAction.PagerSwipe(index = newIndex))
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(index = newIndex),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DotClick should update state and emit UpdatePager`() = runTest {
|
||||
val viewModel = WelcomeViewModel()
|
||||
val newIndex = 2
|
||||
|
||||
viewModel.trySendAction(WelcomeAction.DotClick(index = newIndex))
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(index = newIndex),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
WelcomeEvent.UpdatePager(index = newIndex),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CreateAccountClick should emit NavigateToCreateAccount`() = runTest {
|
||||
val viewModel = WelcomeViewModel()
|
||||
|
||||
viewModel.trySendAction(WelcomeAction.CreateAccountClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
WelcomeEvent.NavigateToCreateAccount,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `LoginClick should emit NavigateToLogin`() = runTest {
|
||||
val viewModel = WelcomeViewModel()
|
||||
|
||||
viewModel.trySendAction(WelcomeAction.LoginClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
WelcomeEvent.NavigateToLogin,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = WelcomeState(
|
||||
index = 0,
|
||||
pages = listOf(
|
||||
WelcomeState.WelcomeCard.CardOne,
|
||||
WelcomeState.WelcomeCard.CardTwo,
|
||||
WelcomeState.WelcomeCard.CardThree,
|
||||
WelcomeState.WelcomeCard.CardFour,
|
||||
),
|
||||
)
|
|
@ -73,6 +73,15 @@ class RootNavScreenTest : BaseComposeTest() {
|
|||
}
|
||||
assertTrue(isSplashScreenRemoved)
|
||||
|
||||
// Make sure navigating to Auth with the welcome route works as expected:
|
||||
rootNavStateFlow.value = RootNavState.AuthWithWelcome
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "welcome",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault locked works as expected:
|
||||
rootNavStateFlow.value = RootNavState.VaultLocked
|
||||
composeTestRule.runOnIdle {
|
||||
|
|
|
@ -22,6 +22,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val authRepository = mockk<AuthRepository> {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { showWelcomeCarousel } returns false
|
||||
}
|
||||
private val specialCircumstanceManager = SpecialCircumstanceManagerImpl()
|
||||
|
||||
|
@ -29,7 +30,22 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
fun `when there are no accounts the nav state should be Auth`() {
|
||||
mutableUserStateFlow.tryEmit(null)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.Auth, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
RootNavState.Auth,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `when there are no accounts and the user has not logged on before the nav state should be Auth with the welcome route`() {
|
||||
every { authRepository.showWelcomeCarousel } returns true
|
||||
mutableUserStateFlow.tryEmit(null)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
RootNavState.AuthWithWelcome,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -57,7 +73,10 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.Auth, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
RootNavState.Auth,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -220,7 +239,10 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.Auth, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
RootNavState.Auth,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -250,7 +272,10 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.Auth, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
RootNavState.Auth,
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in a new issue