Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into item-long-press-delete
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/res/values/strings.xml
  • Loading branch information
SaintPatrck committed Apr 16, 2024
2 parents fb3bf98 + 6d4df64 commit 727ae72
Show file tree
Hide file tree
Showing 26 changed files with 790 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ jobs:
persist-credentials: false

- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1
uses: gradle/wrapper-validation-action@b5418f5a58f5fd2eb486dd7efb368fe7be7eae45 # v2.1.3

- name: Cache Gradle Files
uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: |
~/.gradle/caches
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha }}

- name: Scan with Checkmarx
uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23
uses: checkmarx/ast-github-action@8a59a15b86b4e2f35b974222d1f516eedfe2d585 # 2.0.24
env:
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
with:
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<application
android:name=".AuthenticatorApplication"
android:allowBackup="false"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down Expand Up @@ -47,6 +48,9 @@ class SettingsDiskSourceImpl(
)
}

private val mutableFirstLaunchFlow =
bufferedMutableSharedFlow<Boolean>()

override var appTheme: AppTheme
get() = getString(key = APP_THEME_KEY)
?.let { storedValue ->
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -42,6 +37,7 @@ fun NavGraphBuilder.authenticatorGraph(
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) },
onNavigateToTutorial = { navController.navigateToTutorial() },
)
itemListingGraph(
navController = navController,
Expand All @@ -56,7 +52,8 @@ fun NavGraphBuilder.authenticatorGraph(
},
navigateToEditItem = {
navController.navigateToEditItem(itemId = it)
}
},
navigateToTutorial = { navController.navigateToTutorial() },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,6 +23,7 @@ fun NavGraphBuilder.itemListingGraph(
navigateToQrCodeScanner: () -> Unit,
navigateToManualKeyEntry: () -> Unit,
navigateToEditItem: (String) -> Unit,
navigateToTutorial: () -> Unit,
) {
navigation(
route = ITEM_LISTING_GRAPH_ROUTE,
Expand Down Expand Up @@ -60,7 +60,10 @@ fun NavGraphBuilder.itemListingGraph(
navController.navigateToQrCodeScanScreen()
}
)
settingsGraph(navController)
settingsGraph(
navController = navController,
onNavigateToTutorial = navigateToTutorial
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit,
onNavigateToTutorial: () -> Unit,
) {
composableWithStayTransitions(
route = AUTHENTICATOR_NAV_BAR_ROUTE,
Expand All @@ -22,6 +23,7 @@ fun NavGraphBuilder.authenticatorNavBarDestination(
onNavigateToManualKeyEntry = onNavigateToManualKeyEntry,
onNavigateToEditItem = onNavigateToEditItem,
onNavigateToSearch = onNavigateToSearch,
onNavigateToTutorial = onNavigateToTutorial,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fun AuthenticatorNavBarScreen(
onNavigateToQrCodeScanner: () -> Unit,
onNavigateToManualKeyEntry: () -> Unit,
onNavigateToEditItem: (itemId: String) -> Unit,
onNavigateToTutorial: () -> Unit,
) {
EventsEffect(viewModel = viewModel) { event ->
navController.apply {
Expand Down Expand Up @@ -108,6 +109,7 @@ fun AuthenticatorNavBarScreen(
navigateToQrCodeScanner = onNavigateToQrCodeScanner,
navigateToManualKeyEntry = onNavigateToManualKeyEntry,
navigateToEditItem = onNavigateToEditItem,
navigateToTutorial = onNavigateToTutorial,
)
}

Expand All @@ -121,6 +123,7 @@ private fun AuthenticatorNavBarScaffold(
navigateToQrCodeScanner: () -> Unit,
navigateToManualKeyEntry: () -> Unit,
navigateToEditItem: (itemId: String) -> Unit,
navigateToTutorial: () -> Unit,
) {
BitwardenScaffold(
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.statusBars),
Expand Down Expand Up @@ -166,6 +169,7 @@ private fun AuthenticatorNavBarScaffold(
navigateToQrCodeScanner = navigateToQrCodeScanner,
navigateToManualKeyEntry = navigateToManualKeyEntry,
navigateToEditItem = navigateToEditItem,
navigateToTutorial = navigateToTutorial,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,30 +17,42 @@ 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)
}
}
}

private fun handleBackStackUpdate() {
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 }
}
}
}

Expand All @@ -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.
*/
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
}
}

/**
Expand Down
Loading

0 comments on commit 727ae72

Please sign in to comment.