mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 20:07:59 -06:00
Show Welcome Tutorial on first launch (#27)
This commit is contained in:
parent
b6a165aef5
commit
12e5314c61
@ -34,6 +34,16 @@ interface SettingsDiskSource {
|
||||
*/
|
||||
val isIconLoadingDisabledFlow: Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Tracks whether user has seen the Welcome tutorial.
|
||||
*/
|
||||
var hasSeenWelcomeTutorial: Boolean
|
||||
|
||||
/**
|
||||
* Emits update that track [hasSeenWelcomeTutorial]
|
||||
*/
|
||||
val hasSeenWelcomeTutorialFlow: Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Stores the threshold at which users are alerted that an items validity period is nearing
|
||||
* expiration.
|
||||
|
||||
@ -15,6 +15,7 @@ private const val SCREEN_CAPTURE_ALLOW_KEY = "$BASE_KEY:screenCaptureAllowed"
|
||||
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiometricIntegrityValid"
|
||||
private const val ALERT_THRESHOLD_SECONDS_KEY = "$BASE_KEY:alertThresholdSeconds"
|
||||
private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon"
|
||||
private const val FIRST_LAUNCH_KEY = "$BASE_KEY:hasSeenWelcomeTutorial"
|
||||
|
||||
/**
|
||||
* Primary implementation of [SettingsDiskSource].
|
||||
@ -47,6 +48,9 @@ class SettingsDiskSourceImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private val mutableFirstLaunchFlow =
|
||||
bufferedMutableSharedFlow<Boolean>()
|
||||
|
||||
override var appTheme: AppTheme
|
||||
get() = getString(key = APP_THEME_KEY)
|
||||
?.let { storedValue ->
|
||||
@ -76,6 +80,16 @@ class SettingsDiskSourceImpl(
|
||||
get() = mutableIsIconLoadingDisabledFlow
|
||||
.onSubscription { emit(getBoolean(DISABLE_ICON_LOADING_KEY)) }
|
||||
|
||||
override var hasSeenWelcomeTutorial: Boolean
|
||||
get() = getBoolean(key = FIRST_LAUNCH_KEY) ?: false
|
||||
set(value) {
|
||||
putBoolean(key = FIRST_LAUNCH_KEY, value)
|
||||
mutableFirstLaunchFlow.tryEmit(hasSeenWelcomeTutorial)
|
||||
}
|
||||
|
||||
override val hasSeenWelcomeTutorialFlow: Flow<Boolean>
|
||||
get() = mutableFirstLaunchFlow.onSubscription { emit(hasSeenWelcomeTutorial) }
|
||||
|
||||
override fun storeAlertThresholdSeconds(thresholdSeconds: Int) {
|
||||
putInt(
|
||||
ALERT_THRESHOLD_SECONDS_KEY,
|
||||
|
||||
@ -44,4 +44,14 @@ interface SettingsRepository {
|
||||
* Emits updates that track the [isIconLoadingDisabled] value.
|
||||
*/
|
||||
val isIconLoadingDisabledFlow: Flow<Boolean>
|
||||
|
||||
/**
|
||||
* Whether the user has seen the Welcome tutorial.
|
||||
*/
|
||||
var hasSeenWelcomeTutorial: Boolean
|
||||
|
||||
/**
|
||||
* Tracks whether the user has seen the Welcome tutorial.
|
||||
*/
|
||||
val hasSeenWelcomeTutorialFlow: StateFlow<Boolean>
|
||||
}
|
||||
|
||||
@ -66,4 +66,18 @@ class SettingsRepositoryImpl(
|
||||
.isIconLoadingDisabled
|
||||
?: false,
|
||||
)
|
||||
override var hasSeenWelcomeTutorial: Boolean
|
||||
get() = settingsDiskSource.hasSeenWelcomeTutorial
|
||||
set(value) {
|
||||
settingsDiskSource.hasSeenWelcomeTutorial = value
|
||||
}
|
||||
|
||||
override val hasSeenWelcomeTutorialFlow: StateFlow<Boolean>
|
||||
get() = settingsDiskSource
|
||||
.hasSeenWelcomeTutorialFlow
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = hasSeenWelcomeTutorial,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,22 +1,17 @@
|
||||
package com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navigation
|
||||
import com.x8bit.bitwarden.authenticator.R
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.editItemDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.edititem.navigateToEditItem
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.manualCodeEntryDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry.navigateToManualCodeEntryScreen
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.AUTHENTICATOR_NAV_BAR_ROUTE
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.navbar.authenticatorNavBarDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial.navigateToTutorial
|
||||
|
||||
const val AUTHENTICATOR_GRAPH_ROUTE = "authenticator_graph"
|
||||
|
||||
@ -42,6 +37,7 @@ fun NavGraphBuilder.authenticatorGraph(
|
||||
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
|
||||
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
|
||||
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
|
||||
onNavigateToTutorial = { navController.navigateToTutorial() },
|
||||
)
|
||||
itemListingGraph(
|
||||
navController = navController,
|
||||
@ -56,7 +52,8 @@ fun NavGraphBuilder.authenticatorGraph(
|
||||
},
|
||||
navigateToEditItem = {
|
||||
navController.navigateToEditItem(itemId = it)
|
||||
}
|
||||
},
|
||||
navigateToTutorial = { navController.navigateToTutorial() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.manualcodeentr
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.navigateToQrCodeScanScreen
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.qrCodeScanDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.itemSearchDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.search.navigateToSearch
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.settingsGraph
|
||||
|
||||
const val ITEM_LISTING_GRAPH_ROUTE = "item_listing_graph"
|
||||
@ -24,6 +23,7 @@ fun NavGraphBuilder.itemListingGraph(
|
||||
navigateToQrCodeScanner: () -> Unit,
|
||||
navigateToManualKeyEntry: () -> Unit,
|
||||
navigateToEditItem: (String) -> Unit,
|
||||
navigateToTutorial: () -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
route = ITEM_LISTING_GRAPH_ROUTE,
|
||||
@ -60,7 +60,10 @@ fun NavGraphBuilder.itemListingGraph(
|
||||
navController.navigateToQrCodeScanScreen()
|
||||
}
|
||||
)
|
||||
settingsGraph(navController)
|
||||
settingsGraph(
|
||||
navController = navController,
|
||||
onNavigateToTutorial = navigateToTutorial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
|
||||
onNavigateToQrCodeScanner: () -> Unit,
|
||||
onNavigateToManualKeyEntry: () -> Unit,
|
||||
onNavigateToEditItem: (itemId: String) -> Unit,
|
||||
onNavigateToTutorial: () -> Unit,
|
||||
) {
|
||||
composableWithStayTransitions(
|
||||
route = AUTHENTICATOR_NAV_BAR_ROUTE,
|
||||
@ -22,6 +23,7 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
|
||||
onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
|
||||
onNavigateToEditItem = onNavigateToEditItem,
|
||||
onNavigateToSearch = onNavigateToSearch,
|
||||
onNavigateToTutorial = onNavigateToTutorial,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,6 +71,7 @@ fun AuthenticatorNavBarScreen(
|
||||
onNavigateToQrCodeScanner: () -> Unit,
|
||||
onNavigateToManualKeyEntry: () -> Unit,
|
||||
onNavigateToEditItem: (itemId: String) -> Unit,
|
||||
onNavigateToTutorial: () -> Unit,
|
||||
) {
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
navController.apply {
|
||||
@ -108,6 +109,7 @@ fun AuthenticatorNavBarScreen(
|
||||
navigateToQrCodeScanner = onNavigateToQrCodeScanner,
|
||||
navigateToManualKeyEntry = onNavigateToManualKeyEntry,
|
||||
navigateToEditItem = onNavigateToEditItem,
|
||||
navigateToTutorial = onNavigateToTutorial,
|
||||
)
|
||||
}
|
||||
|
||||
@ -121,6 +123,7 @@ private fun AuthenticatorNavBarScaffold(
|
||||
navigateToQrCodeScanner: () -> Unit,
|
||||
navigateToManualKeyEntry: () -> Unit,
|
||||
navigateToEditItem: (itemId: String) -> Unit,
|
||||
navigateToTutorial: () -> Unit,
|
||||
) {
|
||||
BitwardenScaffold(
|
||||
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars),
|
||||
@ -166,6 +169,7 @@ private fun AuthenticatorNavBarScaffold(
|
||||
navigateToQrCodeScanner = navigateToQrCodeScanner,
|
||||
navigateToManualKeyEntry = navigateToManualKeyEntry,
|
||||
navigateToEditItem = navigateToEditItem,
|
||||
navigateToTutorial = navigateToTutorial,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,9 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.authenticator.
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.SPLASH_ROUTE
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.navigateToSplash
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.splash.splashDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial.TUTORIAL_ROUTE
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial.navigateToTutorial
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial.tutorialDestination
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.theme.NonNullEnterTransitionProvider
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.theme.NonNullExitTransitionProvider
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.theme.RootTransitionProviders
|
||||
@ -62,12 +65,16 @@ fun RootNavScreen(
|
||||
popExitTransition = { toExitTransition()(this) },
|
||||
) {
|
||||
splashDestination()
|
||||
tutorialDestination(
|
||||
onTutorialFinished = { navController.navigateToAuthenticatorGraph() }
|
||||
)
|
||||
authenticatorGraph(navController)
|
||||
}
|
||||
|
||||
val targetRoute = when (state) {
|
||||
RootNavState.ItemListing -> AUTHENTICATOR_GRAPH_ROUTE
|
||||
RootNavState.Splash -> SPLASH_ROUTE
|
||||
RootNavState.Tutorial -> TUTORIAL_ROUTE
|
||||
}
|
||||
|
||||
val currentRoute = navController.currentDestination?.rootLevelRoute()
|
||||
@ -94,8 +101,9 @@ fun RootNavScreen(
|
||||
|
||||
LaunchedEffect(state) {
|
||||
when (state) {
|
||||
RootNavState.ItemListing -> navController.navigateToAuthenticatorGraph(rootNavOptions)
|
||||
RootNavState.Splash -> navController.navigateToSplash(rootNavOptions)
|
||||
RootNavState.Tutorial -> navController.navigateToTutorial(rootNavOptions)
|
||||
RootNavState.ItemListing -> navController.navigateToAuthenticatorGraph(rootNavOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,9 +3,12 @@ package com.x8bit.bitwarden.authenticator.ui.platform.feature.rootnav
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.authenticator.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@ -14,21 +17,29 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class RootNavViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) : BaseViewModel<RootNavState, Unit, RootNavAction>(
|
||||
initialState = RootNavState.Splash
|
||||
) {
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
delay(250)
|
||||
trySendAction(RootNavAction.Internal.StateUpdate)
|
||||
settingsRepository.hasSeenWelcomeTutorialFlow
|
||||
.map { RootNavAction.Internal.HasSeenWelcomeTutorialChange(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: RootNavAction) {
|
||||
when (action) {
|
||||
RootNavAction.BackStackUpdate -> handleBackStackUpdate()
|
||||
RootNavAction.Internal.StateUpdate -> handleStateUpdate()
|
||||
RootNavAction.BackStackUpdate -> {
|
||||
handleBackStackUpdate()
|
||||
}
|
||||
|
||||
is RootNavAction.Internal.HasSeenWelcomeTutorialChange -> {
|
||||
handleHasSeenWelcomeTutorialChange(action.hasSeenWelcomeGuide)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,8 +47,12 @@ class RootNavViewModel @Inject constructor(
|
||||
authRepository.updateLastActiveTime()
|
||||
}
|
||||
|
||||
private fun handleStateUpdate() {
|
||||
mutableStateFlow.update { RootNavState.ItemListing }
|
||||
private fun handleHasSeenWelcomeTutorialChange(hasSeenWelcomeGuide: Boolean) {
|
||||
if (hasSeenWelcomeGuide) {
|
||||
mutableStateFlow.update { RootNavState.ItemListing }
|
||||
} else {
|
||||
mutableStateFlow.update { RootNavState.Tutorial }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +67,12 @@ sealed class RootNavState : Parcelable {
|
||||
@Parcelize
|
||||
data object Splash : RootNavState()
|
||||
|
||||
/**
|
||||
* App should display the Tutorial nav graph.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Tutorial : RootNavState()
|
||||
|
||||
/**
|
||||
* App should display the Account List nav graph.
|
||||
*/
|
||||
@ -68,7 +89,14 @@ sealed class RootNavAction {
|
||||
*/
|
||||
data object BackStackUpdate : RootNavAction()
|
||||
|
||||
/**
|
||||
* Models actions the [RootNavViewModel] itself may send.
|
||||
*/
|
||||
sealed class Internal : RootNavAction() {
|
||||
data object StateUpdate : Internal()
|
||||
|
||||
/**
|
||||
* Indicates an update in the welcome guide being seen has been received.
|
||||
*/
|
||||
data class HasSeenWelcomeTutorialChange(val hasSeenWelcomeGuide: Boolean) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,17 +14,20 @@ private const val SETTINGS_ROUTE = "settings"
|
||||
*/
|
||||
fun NavGraphBuilder.settingsGraph(
|
||||
navController: NavController,
|
||||
onNavigateToTutorial: () -> Unit,
|
||||
) {
|
||||
navigation(
|
||||
startDestination = SETTINGS_ROUTE,
|
||||
route = SETTINGS_GRAPH_ROUTE
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = SETTINGS_ROUTE
|
||||
) {
|
||||
SettingsScreen()
|
||||
}
|
||||
}
|
||||
navigation(
|
||||
startDestination = SETTINGS_ROUTE,
|
||||
route = SETTINGS_GRAPH_ROUTE
|
||||
) {
|
||||
composableWithRootPushTransitions(
|
||||
route = SETTINGS_ROUTE
|
||||
) {
|
||||
SettingsScreen(
|
||||
onNavigateToTutorial = onNavigateToTutorial,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.authenticator.R
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.components.appbar.BitwardenMediumTopAppBar
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
|
||||
@ -47,11 +48,18 @@ import com.x8bit.bitwarden.authenticator.ui.platform.util.displayLabel
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = hiltViewModel(),
|
||||
onNavigateToTutorial: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val scrollBehavior =
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
SettingsEvent.NavigateToTutorial -> onNavigateToTutorial()
|
||||
}
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
topBar = {
|
||||
BitwardenMediumTopAppBar(
|
||||
@ -75,22 +83,32 @@ fun SettingsScreen(
|
||||
}
|
||||
},
|
||||
onThemeSelection = remember(viewModel) {
|
||||
{ viewModel.trySendAction(SettingsAction.AppearanceChange.ThemeChange(it)) }
|
||||
{
|
||||
viewModel.trySendAction(SettingsAction.AppearanceChange.ThemeChange(it))
|
||||
}
|
||||
},
|
||||
onShowWebsiteIconsChange = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
SettingsAction.AppearanceChange.ShowWebsiteIconsChange(
|
||||
it
|
||||
)
|
||||
SettingsAction.AppearanceChange.ShowWebsiteIconsChange(it)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
HelpSettings(
|
||||
onTutorialClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(SettingsAction.HelpClick.ShowTutorialClick)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region Appearance settings
|
||||
|
||||
@Composable
|
||||
private fun AppearanceSettings(
|
||||
state: SettingsState,
|
||||
@ -222,3 +240,25 @@ private fun ThemeSelectionRow(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Appearance settings
|
||||
|
||||
//region Help settings
|
||||
|
||||
@Composable
|
||||
private fun HelpSettings(
|
||||
modifier: Modifier = Modifier,
|
||||
onTutorialClick: () -> Unit,
|
||||
) {
|
||||
BitwardenListHeaderText(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
label = stringResource(id = R.string.help)
|
||||
)
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = R.string.tutorial),
|
||||
onClick = onTutorialClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
//region Help settings
|
||||
|
||||
@ -35,6 +35,7 @@ class SettingsViewModel @Inject constructor(
|
||||
override fun handleAction(action: SettingsAction) {
|
||||
when (action) {
|
||||
is SettingsAction.AppearanceChange -> handleAppearanceChange(action)
|
||||
is SettingsAction.HelpClick -> handleHelpClick(action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +86,16 @@ class SettingsViewModel @Inject constructor(
|
||||
}
|
||||
settingsRepository.appTheme = theme
|
||||
}
|
||||
|
||||
private fun handleHelpClick(action: SettingsAction.HelpClick) {
|
||||
when (action) {
|
||||
SettingsAction.HelpClick.ShowTutorialClick -> handleShowTutorialCLick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShowTutorialCLick() {
|
||||
sendEvent(SettingsEvent.NavigateToTutorial)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,13 +119,19 @@ data class SettingsState(
|
||||
/**
|
||||
* Models events for the settings screen.
|
||||
*/
|
||||
sealed class SettingsEvent
|
||||
sealed class SettingsEvent {
|
||||
data object NavigateToTutorial : SettingsEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the settings screen.
|
||||
*/
|
||||
sealed class SettingsAction {
|
||||
|
||||
sealed class HelpClick : SettingsAction() {
|
||||
data object ShowTutorialClick : HelpClick()
|
||||
}
|
||||
|
||||
sealed class AppearanceChange : SettingsAction() {
|
||||
/**
|
||||
* Indicates the user changed the language.
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.compose.composable
|
||||
|
||||
const val TUTORIAL_ROUTE = "tutorial"
|
||||
|
||||
fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) {
|
||||
composable(TUTORIAL_ROUTE) {
|
||||
TutorialScreen(
|
||||
onTutorialFinished = onTutorialFinished,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavController.navigateToTutorial(navOptions: NavOptions? = null) {
|
||||
navigate(route = TUTORIAL_ROUTE, navOptions = navOptions)
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
package com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.alpha
|
||||
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.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.authenticator.R
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.components.button.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
|
||||
|
||||
private const val INTRO_PAGE = 0
|
||||
private const val QR_SCANNER_PAGE = 1
|
||||
private const val UNIQUE_CODES_PAGE = 2
|
||||
private const val PAGE_COUNT = 3
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TutorialScreen(
|
||||
viewModel: TutorialViewModel = hiltViewModel(),
|
||||
onTutorialFinished: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val pagerState = rememberPagerState(pageCount = { PAGE_COUNT })
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
TutorialEvent.NavigateToAuthenticator -> {
|
||||
onTutorialFinished()
|
||||
}
|
||||
|
||||
TutorialEvent.NavigateToQrScannerSlide -> {
|
||||
pagerState.animateScrollToPage(page = QR_SCANNER_PAGE)
|
||||
}
|
||||
|
||||
TutorialEvent.NavigateToUniqueCodesSlide -> {
|
||||
pagerState.animateScrollToPage(page = UNIQUE_CODES_PAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
HorizontalPager(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
.weight(1f),
|
||||
state = pagerState,
|
||||
userScrollEnabled = true,
|
||||
) { page ->
|
||||
viewModel.trySendAction(
|
||||
TutorialAction.TutorialPageChange(pagerState.targetPage)
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
when (page) {
|
||||
INTRO_PAGE -> VerificationCodesContent()
|
||||
|
||||
QR_SCANNER_PAGE -> TutorialQrScannerScreen()
|
||||
|
||||
UNIQUE_CODES_PAGE -> UniqueCodesContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
) {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(50.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
repeat(PAGE_COUNT) {
|
||||
val color = if (pagerState.currentPage == it) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.background(color, CircleShape)
|
||||
.size(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
BitwardenFilledTonalButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = state.continueButtonText(),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
TutorialAction.ContinueClick
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val alpha = remember(state) {
|
||||
if (state.isLastPage) {
|
||||
0f
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
}
|
||||
BitwardenTextButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(alpha),
|
||||
isEnabled = !state.isLastPage,
|
||||
label = stringResource(id = R.string.skip),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(TutorialAction.SkipClick)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VerificationCodesContent() {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_tutorial_verification_codes),
|
||||
contentDescription = stringResource(
|
||||
id = R.string.secure_your_accounts_with_bitwarden_authenticator,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(R.string.secure_your_accounts_with_bitwarden_authenticator),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(R.string.get_verification_codes_for_all_your_accounts),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TutorialQrScannerScreen() {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_tutorial_qr_scanner),
|
||||
contentDescription = stringResource(id = R.string.scan_qr_code),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(
|
||||
R.string.use_your_device_camera_to_scan_codes
|
||||
),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(
|
||||
R.string.scan_the_qr_code_in_your_2_step_verification_settings_for_any_account
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UniqueCodesContent() {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_tutorial_2fa),
|
||||
contentDescription = stringResource(id = R.string.unique_codes)
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(R.string.sign_in_using_unique_codes),
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
text = stringResource(
|
||||
R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TutorialScreenPreview() {
|
||||
Box {
|
||||
TutorialScreen(
|
||||
onTutorialFinished = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
package com.x8bit.bitwarden.authenticator.ui.platform.feature.tutorial
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.authenticator.R
|
||||
import com.x8bit.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* View model for the [TutorialScreen].
|
||||
*/
|
||||
@HiltViewModel
|
||||
class TutorialViewModel @Inject constructor(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) :
|
||||
BaseViewModel<TutorialState, TutorialEvent, TutorialAction>(
|
||||
initialState = TutorialState.IntroSlide
|
||||
) {
|
||||
|
||||
override fun handleAction(action: TutorialAction) {
|
||||
when (action) {
|
||||
TutorialAction.ContinueClick -> {
|
||||
handleContinueClick()
|
||||
}
|
||||
|
||||
TutorialAction.SkipClick -> {
|
||||
handleSkipClick()
|
||||
}
|
||||
|
||||
is TutorialAction.TutorialPageChange -> {
|
||||
handleTutorialPageChange(action.targetPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleTutorialPageChange(page: Int) {
|
||||
when (page) {
|
||||
0 -> mutableStateFlow.update { TutorialState.IntroSlide }
|
||||
1 -> mutableStateFlow.update { TutorialState.QrScannerSlide }
|
||||
2 -> mutableStateFlow.update { TutorialState.UniqueCodesSlide }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleContinueClick() {
|
||||
val currentPage = mutableStateFlow.value
|
||||
val event = when (currentPage) {
|
||||
TutorialState.IntroSlide -> TutorialEvent.NavigateToQrScannerSlide
|
||||
TutorialState.QrScannerSlide -> TutorialEvent.NavigateToUniqueCodesSlide
|
||||
TutorialState.UniqueCodesSlide -> {
|
||||
settingsRepository.hasSeenWelcomeTutorial = true
|
||||
TutorialEvent.NavigateToAuthenticator
|
||||
}
|
||||
}
|
||||
sendEvent(event)
|
||||
}
|
||||
|
||||
private fun handleSkipClick() {
|
||||
settingsRepository.hasSeenWelcomeTutorial = true
|
||||
sendEvent(TutorialEvent.NavigateToAuthenticator)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state for the Tutorial screen.
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class TutorialState(
|
||||
val continueButtonText: Text,
|
||||
val isLastPage: Boolean,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Tutorial should display the introduction slide.
|
||||
*/
|
||||
@Parcelize
|
||||
data object IntroSlide : TutorialState(
|
||||
continueButtonText = R.string.continue_button.asText(),
|
||||
isLastPage = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Tutorial should display the QR code scanner description slide.
|
||||
*/
|
||||
@Parcelize
|
||||
data object QrScannerSlide : TutorialState(
|
||||
continueButtonText = R.string.continue_button.asText(),
|
||||
isLastPage = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Tutorial should display the 2FA code description slide.
|
||||
*/
|
||||
@Parcelize
|
||||
data object UniqueCodesSlide : TutorialState(
|
||||
continueButtonText = R.string.get_started.asText(),
|
||||
isLastPage = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a set of events related to the tutorial screen.
|
||||
*/
|
||||
sealed class TutorialEvent {
|
||||
/**
|
||||
* Navigate to the authenticator tutorial slide.
|
||||
*/
|
||||
data object NavigateToAuthenticator : TutorialEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the QR Code scanner tutorial slide.
|
||||
*/
|
||||
data object NavigateToQrScannerSlide : TutorialEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the unique codes tutorial slide.
|
||||
*/
|
||||
data object NavigateToUniqueCodesSlide : TutorialEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions that can be taken on the tutorial screen.
|
||||
*/
|
||||
sealed class TutorialAction {
|
||||
/**
|
||||
* The user has manually changed the tutorial page by swiping.
|
||||
*/
|
||||
data class TutorialPageChange(
|
||||
val targetPage: Int,
|
||||
) : TutorialAction()
|
||||
|
||||
/**
|
||||
* The user clicked the continue button on the introduction slide.
|
||||
*/
|
||||
data object ContinueClick : TutorialAction()
|
||||
|
||||
/**
|
||||
* The user clicked the skip button on one of the tutorial slides.
|
||||
*/
|
||||
data object SkipClick : TutorialAction()
|
||||
}
|
||||
29
app/src/main/res/drawable/ic_tutorial_2fa.xml
Normal file
29
app/src/main/res/drawable/ic_tutorial_2fa.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="214dp"
|
||||
android:height="72dp"
|
||||
android:viewportHeight="72"
|
||||
android:viewportWidth="214">
|
||||
<path
|
||||
android:fillColor="#B2C5FF"
|
||||
android:pathData="M12.51,25.73L9.77,28.85C9.61,29.03 9.52,29.27 9.52,29.51V65.67C9.52,65.98 9.66,66.26 9.9,66.45L13.94,69.68C14.11,69.82 14.33,69.9 14.56,69.9H58.46C58.77,69.9 59.07,69.75 59.26,69.5L62.98,64.54C63.11,64.37 63.18,64.16 63.18,63.94V29.58C63.18,29.3 63.06,29.03 62.84,28.84L59.24,25.64C59.06,25.48 58.82,25.39 58.58,25.39H13.26C12.97,25.39 12.7,25.51 12.51,25.73Z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M0.74,0.5h71v71h-71z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M39.84,47.26C39.85,46.61 39.68,45.97 39.36,45.41C39.04,44.84 38.57,44.38 38.01,44.06C37.46,43.74 36.82,43.58 36.18,43.59C35.54,43.6 34.91,43.78 34.36,44.12C33.82,44.46 33.37,44.94 33.07,45.52C32.76,46.09 32.62,46.74 32.65,47.39C32.68,48.04 32.87,48.66 33.22,49.21C33.57,49.76 34.06,50.2 34.63,50.49V56.47C34.62,56.69 34.65,56.91 34.73,57.12C34.81,57.33 34.92,57.52 35.07,57.68C35.22,57.84 35.41,57.97 35.61,58.05C35.81,58.14 36.03,58.19 36.25,58.19C36.46,58.19 36.68,58.14 36.88,58.05C37.08,57.97 37.27,57.84 37.42,57.68C37.57,57.51 37.69,57.32 37.76,57.12C37.84,56.91 37.87,56.69 37.86,56.47V50.48C38.45,50.18 38.95,49.72 39.3,49.15C39.65,48.58 39.83,47.93 39.84,47.26ZM58.75,23.16H56.76C56.6,23.16 56.45,23.12 56.3,23.06C56.16,23 56.03,22.91 55.92,22.8C55.81,22.69 55.72,22.55 55.66,22.41C55.6,22.26 55.57,22.1 55.57,21.94V21.06C55.66,16.08 53.94,11.24 50.73,7.46C47.52,3.68 43.05,1.24 38.17,0.59C35.48,0.33 32.75,0.64 30.19,1.51C27.62,2.37 25.26,3.77 23.25,5.62C21.25,7.47 19.65,9.72 18.56,12.23C17.47,14.74 16.91,17.45 16.91,20.2V21.63C16.91,21.66 16.81,23.15 15.53,23.17H13.74C12.15,23.17 10.63,23.82 9.5,24.96C8.38,26.1 7.75,27.65 7.75,29.26V65.39C7.75,67.01 8.38,68.55 9.5,69.7C10.63,70.84 12.15,71.49 13.74,71.5H58.75C60.34,71.49 61.86,70.84 62.98,69.7C64.1,68.56 64.73,67.01 64.73,65.4V29.26C64.73,28.46 64.58,27.67 64.28,26.93C63.98,26.19 63.54,25.52 62.99,24.95C62.43,24.39 61.77,23.94 61.04,23.63C60.32,23.32 59.54,23.16 58.75,23.16L58.75,23.16ZM21.3,20.2C21.29,17.93 21.78,15.7 22.74,13.65C23.69,11.61 25.09,9.8 26.83,8.38C28.56,6.95 30.59,5.93 32.76,5.4C34.93,4.86 37.19,4.83 39.38,5.29C42.78,6.1 45.81,8.06 47.96,10.85C50.11,13.65 51.25,17.1 51.19,20.63V21.94C51.19,22.1 51.16,22.26 51.1,22.41C51.04,22.55 50.95,22.69 50.84,22.8C50.73,22.91 50.6,23 50.45,23.06C50.31,23.12 50.15,23.16 49.99,23.16H23.13C22.91,23.18 22.68,23.15 22.47,23.08C22.26,23.01 22.06,22.9 21.89,22.76C21.72,22.61 21.58,22.43 21.47,22.23C21.37,22.03 21.31,21.81 21.29,21.58V20.2H21.3ZM60.35,65.39C60.35,65.83 60.18,66.24 59.88,66.55C59.58,66.86 59.17,67.03 58.75,67.03H13.74C13.53,67.03 13.32,66.99 13.13,66.9C12.93,66.82 12.75,66.7 12.6,66.55C12.45,66.4 12.34,66.22 12.26,66.02C12.18,65.82 12.14,65.61 12.14,65.39V29.26C12.13,29.04 12.18,28.83 12.26,28.63C12.33,28.44 12.45,28.26 12.6,28.1C12.75,27.95 12.93,27.83 13.12,27.75C13.32,27.67 13.52,27.62 13.73,27.62H58.75C59.17,27.63 59.58,27.8 59.88,28.11C60.18,28.41 60.35,28.82 60.35,29.26V65.39L60.35,65.39Z" />
|
||||
</group>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M87.48,13L210.26,13A1,1 0,0 1,211.26 14L211.26,62A1,1 0,0 1,210.26 63L87.48,63A1,1 0,0 1,86.48 62L86.48,14A1,1 0,0 1,87.48 13z"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeWidth="5" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M174.17,48.66C173.85,49.09 173.23,49.18 172.79,48.87L172.18,48.43C171.75,48.12 171.63,47.52 171.93,47.07L176.37,40.29C176.71,39.77 176.5,39.07 175.93,38.82L169.45,36.02C168.97,35.82 168.73,35.27 168.9,34.78L169.1,34.19C169.28,33.68 169.84,33.4 170.36,33.57L176.86,35.71C177.49,35.92 178.14,35.47 178.17,34.81L178.53,27.25C178.56,26.71 179,26.3 179.53,26.3H180.12C180.65,26.3 181.09,26.71 181.12,27.25L181.48,34.81C181.51,35.47 182.16,35.92 182.79,35.71L189.29,33.57C189.81,33.4 190.37,33.68 190.55,34.19L190.75,34.78C190.92,35.27 190.68,35.82 190.2,36.02L183.72,38.82C183.15,39.07 182.94,39.77 183.28,40.29L187.72,47.07C188.02,47.52 187.9,48.12 187.47,48.43L186.86,48.87C186.42,49.18 185.8,49.09 185.48,48.66L180.62,42.22C180.22,41.69 179.43,41.69 179.03,42.22L174.17,48.66Z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M142.99,48.66C142.67,49.09 142.05,49.18 141.61,48.87L141,48.43C140.57,48.12 140.45,47.52 140.75,47.07L145.19,40.29C145.53,39.77 145.32,39.07 144.75,38.82L138.27,36.02C137.79,35.82 137.55,35.27 137.72,34.78L137.92,34.19C138.1,33.68 138.66,33.4 139.18,33.57L145.68,35.71C146.31,35.92 146.96,35.47 146.99,34.81L147.35,27.25C147.38,26.71 147.82,26.3 148.35,26.3H148.94C149.47,26.3 149.91,26.71 149.94,27.25L150.3,34.81C150.33,35.47 150.98,35.92 151.61,35.71L158.11,33.57C158.63,33.4 159.19,33.68 159.37,34.19L159.57,34.78C159.74,35.27 159.51,35.82 159.02,36.02L152.54,38.82C151.98,39.07 151.76,39.77 152.1,40.29L156.54,47.07C156.84,47.52 156.73,48.12 156.29,48.43L155.68,48.87C155.24,49.18 154.63,49.09 154.3,48.66L149.44,42.22C149.04,41.69 148.25,41.69 147.85,42.22L142.99,48.66Z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M111.81,48.66C111.49,49.09 110.88,49.18 110.43,48.87L109.82,48.43C109.39,48.12 109.27,47.52 109.57,47.07L114.01,40.29C114.35,39.77 114.14,39.07 113.57,38.82L107.09,36.02C106.61,35.82 106.37,35.27 106.54,34.78L106.74,34.19C106.92,33.68 107.48,33.4 108,33.57L114.5,35.71C115.13,35.92 115.78,35.47 115.81,34.81L116.17,27.25C116.2,26.71 116.64,26.3 117.17,26.3H117.76C118.29,26.3 118.73,26.71 118.76,27.25L119.12,34.81C119.15,35.47 119.8,35.92 120.43,35.71L126.93,33.57C127.45,33.4 128.01,33.68 128.19,34.19L128.39,34.78C128.57,35.27 128.33,35.82 127.85,36.02L121.36,38.82C120.79,39.07 120.58,39.77 120.93,40.29L125.36,47.07C125.66,47.52 125.54,48.12 125.11,48.43L124.5,48.87C124.06,49.18 123.45,49.09 123.12,48.66L118.26,42.22C117.86,41.69 117.07,41.69 116.67,42.22L111.81,48.66Z" />
|
||||
</vector>
|
||||
93
app/src/main/res/drawable/ic_tutorial_qr_scanner.xml
Normal file
93
app/src/main/res/drawable/ic_tutorial_qr_scanner.xml
Normal file
@ -0,0 +1,93 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="146dp"
|
||||
android:height="147dp"
|
||||
android:viewportWidth="146"
|
||||
android:viewportHeight="147">
|
||||
<path
|
||||
android:pathData="M3,119.64V142.19C3,142.74 3.45,143.19 4,143.19H26.55"
|
||||
android:strokeWidth="6"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M143,26.55V4C143,3.45 142.55,3 142,3L119.45,3"
|
||||
android:strokeWidth="6"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M119.45,143.19H142C142.55,143.19 143,142.74 143,142.19V119.64"
|
||||
android:strokeWidth="6"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M26.55,3L4,3C3.45,3 3,3.45 3,4L3,26.55"
|
||||
android:strokeWidth="6"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M20.22,23.16L57.2,23.16A1,1 0,0 1,58.2 24.16L58.2,61.14A1,1 0,0 1,57.2 62.14L20.22,62.14A1,1 0,0 1,19.22 61.14L19.22,24.16A1,1 0,0 1,20.22 23.16z"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M29.96,32.91L47.45,32.91A1,1 0,0 1,48.45 33.91L48.45,51.4A1,1 0,0 1,47.45 52.4L29.96,52.4A1,1 0,0 1,28.96 51.4L28.96,33.91A1,1 0,0 1,29.96 32.91z"
|
||||
android:fillColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M84.18,23.16H122.15C122.71,23.16 123.15,23.61 123.15,24.16V62.14"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M94.92,32.91L112.41,32.91A1,1 0,0 1,113.41 33.91L113.41,51.4A1,1 0,0 1,112.41 52.4L94.92,52.4A1,1 0,0 1,93.92 51.4L93.92,33.91A1,1 0,0 1,94.92 32.91z"
|
||||
android:fillColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M20.22,88.12L57.2,88.12A1,1 0,0 1,58.2 89.12L58.2,126.1A1,1 0,0 1,57.2 127.1L20.22,127.1A1,1 0,0 1,19.22 126.1L19.22,89.12A1,1 0,0 1,20.22 88.12z"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M29.96,97.87L47.45,97.87A1,1 0,0 1,48.45 98.87L48.45,116.36A1,1 0,0 1,47.45 117.36L29.96,117.36A1,1 0,0 1,28.96 116.36L28.96,98.87A1,1 0,0 1,29.96 97.87z"
|
||||
android:fillColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M85.18,88.12L122.15,88.12A1,1 0,0 1,123.15 89.12L123.15,126.1A1,1 0,0 1,122.15 127.1L85.18,127.1A1,1 0,0 1,84.18 126.1L84.18,89.12A1,1 0,0 1,85.18 88.12z"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M94.92,97.87L112.41,97.87A1,1 0,0 1,113.41 98.87L113.41,116.36A1,1 0,0 1,112.41 117.36L94.92,117.36A1,1 0,0 1,93.92 116.36L93.92,98.87A1,1 0,0 1,94.92 97.87z"
|
||||
android:fillColor="#0055D4"/>
|
||||
<path
|
||||
android:pathData="M71.21,23.76V59.87"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M71.21,86.79V127.77"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M18.84,74.95H52.33"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M70.81,74.95H94.55"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M108.16,74.95H123.78"
|
||||
android:strokeWidth="5"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#0055D4"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
29
app/src/main/res/drawable/ic_tutorial_unique_codes.xml
Normal file
29
app/src/main/res/drawable/ic_tutorial_unique_codes.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="214dp"
|
||||
android:height="140dp"
|
||||
android:viewportHeight="140"
|
||||
android:viewportWidth="214">
|
||||
<path
|
||||
android:fillColor="#B2C5FF"
|
||||
android:pathData="M12.51,59.73L9.77,62.85C9.61,63.03 9.52,63.27 9.52,63.51V99.67C9.52,99.98 9.66,100.26 9.9,100.45L13.94,103.68C14.11,103.82 14.33,103.9 14.56,103.9H58.46C58.77,103.9 59.07,103.75 59.26,103.5L62.98,98.54C63.11,98.37 63.18,98.16 63.18,97.94V63.58C63.18,63.3 63.06,63.03 62.84,62.84L59.24,59.64C59.06,59.48 58.82,59.39 58.58,59.39H13.26C12.97,59.39 12.7,59.51 12.51,59.73Z" />
|
||||
<group>
|
||||
<clip-path android:pathData="M0.74,34.5h71v71h-71z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M39.84,81.26C39.85,80.61 39.68,79.97 39.36,79.41C39.04,78.84 38.57,78.38 38.01,78.06C37.46,77.74 36.82,77.58 36.18,77.59C35.54,77.6 34.91,77.78 34.36,78.12C33.82,78.46 33.37,78.94 33.07,79.52C32.76,80.09 32.62,80.74 32.65,81.39C32.68,82.04 32.87,82.66 33.22,83.21C33.57,83.76 34.06,84.2 34.63,84.49V90.46C34.62,90.69 34.65,90.91 34.73,91.12C34.81,91.33 34.92,91.52 35.07,91.68C35.22,91.84 35.41,91.97 35.61,92.05C35.81,92.14 36.03,92.19 36.25,92.19C36.46,92.19 36.68,92.14 36.88,92.05C37.08,91.97 37.27,91.84 37.42,91.68C37.57,91.51 37.69,91.32 37.76,91.12C37.84,90.91 37.87,90.69 37.86,90.46V84.48C38.45,84.18 38.95,83.72 39.3,83.15C39.65,82.58 39.83,81.93 39.84,81.26ZM58.75,57.16H56.76C56.6,57.16 56.45,57.12 56.3,57.06C56.16,57 56.03,56.91 55.92,56.8C55.81,56.69 55.72,56.55 55.66,56.41C55.6,56.26 55.57,56.1 55.57,55.94V55.06C55.66,50.08 53.94,45.24 50.73,41.46C47.52,37.68 43.05,35.24 38.17,34.59C35.48,34.33 32.75,34.64 30.19,35.51C27.62,36.37 25.26,37.77 23.25,39.62C21.25,41.47 19.65,43.72 18.56,46.23C17.47,48.74 16.91,51.45 16.91,54.2V55.63C16.91,55.66 16.81,57.15 15.53,57.17H13.74C12.15,57.17 10.63,57.82 9.5,58.96C8.38,60.1 7.75,61.65 7.75,63.26V99.39C7.75,101.01 8.38,102.55 9.5,103.7C10.63,104.84 12.15,105.49 13.74,105.5H58.75C60.34,105.49 61.86,104.84 62.98,103.7C64.1,102.56 64.73,101.01 64.73,99.4V63.26C64.73,62.46 64.58,61.67 64.28,60.93C63.98,60.19 63.54,59.52 62.99,58.95C62.43,58.39 61.77,57.94 61.04,57.63C60.32,57.32 59.54,57.16 58.75,57.16L58.75,57.16ZM21.3,54.2C21.29,51.93 21.78,49.7 22.74,47.65C23.69,45.61 25.09,43.8 26.83,42.38C28.56,40.95 30.59,39.93 32.76,39.4C34.93,38.86 37.19,38.83 39.38,39.29C42.78,40.1 45.81,42.06 47.96,44.85C50.11,47.65 51.25,51.1 51.19,54.63V55.94C51.19,56.1 51.16,56.26 51.1,56.41C51.04,56.55 50.95,56.69 50.84,56.8C50.73,56.91 50.6,57 50.45,57.06C50.31,57.12 50.15,57.16 49.99,57.16H23.13C22.91,57.18 22.68,57.15 22.47,57.08C22.26,57.01 22.06,56.9 21.89,56.76C21.72,56.61 21.58,56.43 21.47,56.23C21.37,56.03 21.31,55.81 21.29,55.58V54.2H21.3ZM60.35,99.39C60.35,99.83 60.18,100.24 59.88,100.55C59.58,100.86 59.17,101.03 58.75,101.03H13.74C13.53,101.03 13.32,100.99 13.13,100.9C12.93,100.82 12.75,100.7 12.6,100.55C12.45,100.4 12.34,100.22 12.26,100.02C12.18,99.82 12.14,99.61 12.14,99.39V63.26C12.13,63.04 12.18,62.83 12.26,62.63C12.33,62.44 12.45,62.26 12.6,62.1C12.75,61.95 12.93,61.83 13.12,61.75C13.32,61.67 13.52,61.62 13.73,61.62H58.75C59.17,61.63 59.58,61.8 59.88,62.11C60.18,62.41 60.35,62.82 60.35,63.26V99.39L60.35,99.39Z" />
|
||||
</group>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M87.48,47L210.26,47A1,1 0,0 1,211.26 48L211.26,96A1,1 0,0 1,210.26 97L87.48,97A1,1 0,0 1,86.48 96L86.48,48A1,1 0,0 1,87.48 47z"
|
||||
android:strokeColor="#B2C5FF"
|
||||
android:strokeWidth="5" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M174.17,82.66C173.85,83.09 173.23,83.18 172.79,82.87L172.18,82.43C171.75,82.12 171.63,81.52 171.93,81.07L176.37,74.29C176.71,73.77 176.5,73.07 175.93,72.82L169.45,70.02C168.97,69.82 168.73,69.27 168.9,68.78L169.1,68.19C169.28,67.68 169.84,67.4 170.36,67.57L176.86,69.71C177.49,69.92 178.14,69.47 178.17,68.81L178.53,61.25C178.56,60.71 179,60.3 179.53,60.3H180.12C180.65,60.3 181.09,60.71 181.12,61.25L181.48,68.81C181.51,69.47 182.16,69.92 182.79,69.71L189.29,67.57C189.81,67.4 190.37,67.68 190.55,68.19L190.75,68.78C190.92,69.27 190.68,69.82 190.2,70.02L183.72,72.82C183.15,73.07 182.94,73.77 183.28,74.29L187.72,81.07C188.02,81.52 187.9,82.12 187.47,82.43L186.86,82.87C186.42,83.18 185.8,83.09 185.48,82.66L180.62,76.22C180.22,75.69 179.43,75.69 179.03,76.22L174.17,82.66Z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M142.99,82.66C142.67,83.09 142.05,83.18 141.61,82.87L141,82.43C140.57,82.12 140.45,81.52 140.75,81.07L145.19,74.29C145.53,73.77 145.32,73.07 144.75,72.82L138.27,70.02C137.79,69.82 137.55,69.27 137.72,68.78L137.92,68.19C138.1,67.68 138.66,67.4 139.18,67.57L145.68,69.71C146.31,69.92 146.96,69.47 146.99,68.81L147.35,61.25C147.38,60.71 147.82,60.3 148.35,60.3H148.94C149.47,60.3 149.91,60.71 149.94,61.25L150.3,68.81C150.33,69.47 150.98,69.92 151.61,69.71L158.11,67.57C158.63,67.4 159.19,67.68 159.37,68.19L159.57,68.78C159.74,69.27 159.51,69.82 159.02,70.02L152.54,72.82C151.98,73.07 151.76,73.77 152.1,74.29L156.54,81.07C156.84,81.52 156.73,82.12 156.29,82.43L155.68,82.87C155.24,83.18 154.63,83.09 154.3,82.66L149.44,76.22C149.04,75.69 148.25,75.69 147.85,76.22L142.99,82.66Z" />
|
||||
<path
|
||||
android:fillColor="#0055D4"
|
||||
android:pathData="M111.81,82.66C111.49,83.09 110.88,83.18 110.43,82.87L109.82,82.43C109.39,82.12 109.27,81.52 109.57,81.07L114.01,74.29C114.35,73.77 114.14,73.07 113.57,72.82L107.09,70.02C106.61,69.82 106.37,69.27 106.54,68.78L106.74,68.19C106.92,67.68 107.48,67.4 108,67.57L114.5,69.71C115.13,69.92 115.78,69.47 115.81,68.81L116.17,61.25C116.2,60.71 116.64,60.3 117.17,60.3H117.76C118.29,60.3 118.73,60.71 118.76,61.25L119.12,68.81C119.15,69.47 119.8,69.92 120.43,69.71L126.93,67.57C127.45,67.4 128.01,67.68 128.19,68.19L128.39,68.78C128.57,69.27 128.33,69.82 127.85,70.02L121.36,72.82C120.79,73.07 120.58,73.77 120.93,74.29L125.36,81.07C125.66,81.52 125.54,82.12 125.11,82.43L124.5,82.87C124.06,83.18 123.45,83.09 123.12,82.66L118.26,76.22C117.86,75.69 117.07,75.69 116.67,76.22L111.81,82.66Z" />
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_tutorial_verification_codes.xml
Normal file
14
app/src/main/res/drawable/ic_tutorial_verification_codes.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="140dp"
|
||||
android:height="140dp"
|
||||
android:viewportWidth="140"
|
||||
android:viewportHeight="140">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h140v140h-140z"/>
|
||||
<path
|
||||
android:pathData="M37.07,30.68C58.79,8.96 94,8.96 115.71,30.68C137.43,52.4 137.43,87.61 115.71,109.32C102.96,122.08 85.55,127.34 68.94,125.11C66.75,124.82 64.74,126.35 64.45,128.54C64.15,130.73 65.69,132.74 67.88,133.04C86.85,135.59 106.78,129.57 121.37,114.98C146.21,90.14 146.21,49.86 121.37,25.02C96.53,0.18 56.25,0.18 31.41,25.02C24.15,32.29 19,40.89 15.99,50.03L9.42,38.36C8.34,36.44 5.9,35.75 3.98,36.84C2.05,37.92 1.37,40.36 2.45,42.28L13.74,62.36C14.82,64.28 17.26,64.96 19.19,63.88L39.26,52.59C41.19,51.51 41.87,49.07 40.79,47.15C39.7,45.22 37.26,44.54 35.34,45.62L23.71,52.16C26.36,44.31 30.81,36.94 37.07,30.68ZM89.67,37.38C95.17,37.38 99.66,41.87 99.66,47.37V54.8H79.68V47.37C79.68,41.85 84.16,37.38 89.67,37.38ZM74.88,54.8V47.37C74.88,39.19 81.52,32.58 89.67,32.58C97.82,32.58 104.46,39.22 104.46,47.37V54.8H106.89C109.1,54.8 110.89,56.59 110.89,58.8V87.59C110.89,89.8 109.1,91.59 106.89,91.59H92.37V107.46C92.37,108.38 92.13,109.34 91.61,110.14C91.11,110.92 90.13,111.8 88.71,111.8H3.66C2.26,111.8 1.27,110.95 0.75,110.16C0.22,109.36 -0.03,108.39 0,107.41V77.66C0,76.74 0.24,75.78 0.75,74.98C1.26,74.2 2.23,73.32 3.66,73.32H69.04V58.8C69.04,56.59 70.83,54.8 73.04,54.8H74.88ZM73.84,73.32H88.71C90.13,73.32 91.11,74.2 91.61,74.98C92.13,75.78 92.37,76.74 92.37,77.66V86.79H106.09V59.6H73.84V73.32ZM87.57,86.79V78.12H4.8V107H87.57V91.59H87.46V86.79H87.57ZM4.8,107.6C4.8,107.61 4.8,107.61 4.8,107.61C4.8,107.61 4.8,107.61 4.8,107.61C4.8,107.61 4.8,107.61 4.8,107.6ZM47.05,96.97C47.23,96 47.33,94.84 47.33,93.49C47.33,91 46.97,89.08 46.26,87.73C45.85,86.95 45.34,86.29 44.74,85.77C44.16,85.23 43.47,84.83 42.68,84.56C41.9,84.28 41.02,84.14 40.06,84.14C38.6,84.14 37.34,84.45 36.29,85.08C35.24,85.69 34.44,86.59 33.89,87.78C33.57,88.49 33.34,89.35 33.19,90.34C33.04,91.34 32.96,92.46 32.96,93.7C32.96,94.66 33.03,95.56 33.16,96.39C33.3,97.21 33.51,97.96 33.8,98.64C34.37,99.89 35.22,100.87 36.34,101.58C37.47,102.29 38.75,102.64 40.17,102.64C41.41,102.64 42.53,102.38 43.54,101.85C44.55,101.32 45.37,100.57 46.01,99.6C46.51,98.82 46.86,97.94 47.05,96.97ZM43.17,89.55C43.38,90.51 43.48,91.74 43.48,93.24C43.48,94.83 43.38,96.11 43.19,97.09C42.99,98.07 42.65,98.82 42.16,99.35C41.68,99.87 41,100.14 40.14,100.14C39.31,100.14 38.65,99.88 38.17,99.38C37.68,98.87 37.33,98.12 37.13,97.14C36.92,96.15 36.82,94.89 36.82,93.34C36.82,91.06 37.06,89.38 37.53,88.28C38.02,87.19 38.88,86.64 40.12,86.64C40.98,86.64 41.65,86.89 42.14,87.38C42.62,87.87 42.97,88.59 43.17,89.55ZM18.8,89.44V100.67C18.8,101.32 18.97,101.82 19.32,102.16C19.67,102.5 20.12,102.66 20.69,102.66C21.98,102.66 22.62,101.84 22.62,100.18V86.06C22.62,85.47 22.47,85.01 22.17,84.67C21.87,84.33 21.47,84.16 20.98,84.16C20.54,84.16 20.24,84.24 20.08,84.39C19.92,84.53 19.58,84.93 19.05,85.57C18.53,86.21 17.93,86.79 17.24,87.31C16.57,87.83 15.67,88.32 14.54,88.79C13.78,89.1 13.25,89.36 12.95,89.55C12.65,89.75 12.5,90.06 12.5,90.48C12.5,90.84 12.65,91.16 12.95,91.44C13.26,91.71 13.61,91.85 14,91.85C14.83,91.85 16.43,91.05 18.8,89.44ZM60.64,100.67V89.44C58.27,91.05 56.67,91.85 55.85,91.85C55.45,91.85 55.1,91.71 54.79,91.44C54.5,91.16 54.35,90.84 54.35,90.48C54.35,90.06 54.5,89.75 54.79,89.55C55.09,89.36 55.62,89.1 56.38,88.79C57.51,88.32 58.41,87.83 59.09,87.31C59.77,86.79 60.37,86.21 60.9,85.57C61.42,84.93 61.76,84.53 61.92,84.39C62.08,84.24 62.38,84.16 62.82,84.16C63.31,84.16 63.71,84.33 64.01,84.67C64.31,85.01 64.46,85.47 64.46,86.06V100.18C64.46,101.84 63.82,102.66 62.54,102.66C61.97,102.66 61.51,102.5 61.16,102.16C60.82,101.82 60.64,101.32 60.64,100.67ZM77.73,89.44V100.67C77.73,101.32 77.9,101.82 78.25,102.16C78.59,102.5 79.05,102.66 79.62,102.66C80.9,102.66 81.55,101.84 81.55,100.18V86.06C81.55,85.47 81.4,85.01 81.1,84.67C80.8,84.33 80.4,84.16 79.9,84.16C79.46,84.16 79.17,84.24 79.01,84.39C78.85,84.53 78.51,84.93 77.98,85.57C77.46,86.21 76.86,86.79 76.17,87.31C75.5,87.83 74.6,88.32 73.47,88.79C72.71,89.1 72.18,89.36 71.88,89.55C71.58,89.75 71.43,90.06 71.43,90.48C71.43,90.84 71.58,91.16 71.88,91.44C72.19,91.71 72.54,91.85 72.93,91.85C73.76,91.85 75.35,91.05 77.73,89.44Z"
|
||||
android:fillColor="#175DDC"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
@ -74,4 +74,16 @@
|
||||
<string name="language_change_x_description">The language has been changed to %1$s. Please restart the app to see the change</string>
|
||||
<string name="show_website_icons">Show website icons</string>
|
||||
<string name="show_website_icons_description">Show a recognizable image next to each login.</string>
|
||||
<string name="secure_your_accounts_with_bitwarden_authenticator">Secure your accounts with Bitwarden Authneticator</string>
|
||||
<string name="get_verification_codes_for_all_your_accounts">Get verification codes for all your accounts that support 2-step verification.</string>
|
||||
<string name="use_your_device_camera_to_scan_codes">Use your device camera to scan codes</string>
|
||||
<string name="scan_the_qr_code_in_your_2_step_verification_settings_for_any_account">Scan the QR code in your 2-step verification settings for any account.</string>
|
||||
<string name="sign_in_using_unique_codes">Sign in using unique codes</string>
|
||||
<string name="when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app">When using 2-step verification, you’ll enter your username and password and a code generated in this app.</string>
|
||||
<string name="continue_button">Continue</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="get_started">Get started</string>
|
||||
<string name="unique_codes">Uniqe codes</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="tutorial">Tutorial</string>
|
||||
</resources>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user