Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/main' into tutorial…
Browse files Browse the repository at this point in the history
…-base

# Conflicts:
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSource.kt
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/data/platform/repository/SettingsRepository.kt
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/data/platform/repository/SettingsRepositoryImpl.kt
#	app/src/main/res/values/strings.xml
  • Loading branch information
SaintPatrck committed Apr 14, 2024
2 parents 0891635 + b6a165a commit bf062e5
Show file tree
Hide file tree
Showing 19 changed files with 1,037 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.authenticator.data.platform.datasource.disk

import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.flow.Flow

Expand All @@ -8,6 +9,11 @@ import kotlinx.coroutines.flow.Flow
*/
interface SettingsDiskSource {

/**
* The currently persisted app language (or `null` if not set).
*/
var appLanguage: AppLanguage?

/**
* The currently persisted app theme (or `null` if not set).
*/
Expand All @@ -18,6 +24,16 @@ interface SettingsDiskSource {
*/
val appThemeFlow: Flow<AppTheme>

/**
* The currently persisted setting for getting login item icons (or `null` if not set).
*/
var isIconLoadingDisabled: Boolean?

/**
* Emits updates that track [isIconLoadingDisabled].
*/
val isIconLoadingDisabledFlow: Flow<Boolean?>

/**
* Tracks whether user has seen the Welcome tutorial.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package com.x8bit.bitwarden.authenticator.data.platform.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.authenticator.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onSubscription

private const val APP_THEME_KEY = "$BASE_KEY:theme"
private const val APP_LANGUAGE_KEY = "$BASE_KEY:appLocale"
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"

/**
Expand All @@ -27,9 +30,24 @@ class SettingsDiskSourceImpl(
private val mutableScreenCaptureAllowedFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()

private val mutableIsIconLoadingDisabledFlow =
bufferedMutableSharedFlow<Boolean?>()

private val mutableAlertThresholdSecondsFlow =
bufferedMutableSharedFlow<Int>()

override var appLanguage: AppLanguage?
get() = getString(key = APP_LANGUAGE_KEY)
?.let { storedValue ->
AppLanguage.entries.firstOrNull { storedValue == it.localeName }
}
set(value) {
putString(
key = APP_LANGUAGE_KEY,
value = value?.localeName,
)
}

private val mutableFirstLaunchFlow =
bufferedMutableSharedFlow<Boolean>()

Expand All @@ -51,6 +69,17 @@ class SettingsDiskSourceImpl(
get() = mutableAppThemeFlow
.onSubscription { emit(appTheme) }

override var isIconLoadingDisabled: Boolean?
get() = getBoolean(key = DISABLE_ICON_LOADING_KEY)
set(value) {
putBoolean(key = DISABLE_ICON_LOADING_KEY, value = value)
mutableIsIconLoadingDisabledFlow.tryEmit(value)
}

override val isIconLoadingDisabledFlow: Flow<Boolean?>
get() = mutableIsIconLoadingDisabledFlow
.onSubscription { emit(getBoolean(DISABLE_ICON_LOADING_KEY)) }

override var hasSeenWelcomeTutorial: Boolean
get() = getBoolean(key = FIRST_LAUNCH_KEY) ?: false
set(value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.x8bit.bitwarden.authenticator.data.platform.repository

import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

/**
* Provides an API for observing and modifying settings state.
*/
interface SettingsRepository {

/**
* The [AppLanguage] for the current user.
*/
var appLanguage: AppLanguage

/**
* The currently stored [AppTheme].
*/
Expand All @@ -28,6 +35,16 @@ interface SettingsRepository {
*/
val authenticatorAlertThresholdSecondsFlow: StateFlow<Int>

/**
* The current setting for getting login item icons.
*/
var isIconLoadingDisabled: Boolean

/**
* Emits updates that track the [isIconLoadingDisabled] value.
*/
val isIconLoadingDisabledFlow: Flow<Boolean>

/**
* Whether the user has seen the Welcome tutorial.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.x8bit.bitwarden.authenticator.data.platform.repository

import com.x8bit.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.authenticator.data.platform.manager.DispatcherManager
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
Expand All @@ -19,6 +21,12 @@ class SettingsRepositoryImpl(

private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)

override var appLanguage: AppLanguage
get() = settingsDiskSource.appLanguage ?: AppLanguage.DEFAULT
set(value) {
settingsDiskSource.appLanguage = value
}

override var appTheme: AppTheme by settingsDiskSource::appTheme

override var authenticatorAlertThresholdSeconds = settingsDiskSource.getAlertThresholdSeconds()
Expand All @@ -41,7 +49,23 @@ class SettingsRepositoryImpl(
started = SharingStarted.Eagerly,
initialValue = settingsDiskSource.getAlertThresholdSeconds(),
)
override var isIconLoadingDisabled: Boolean
get() = settingsDiskSource.isIconLoadingDisabled ?: false
set(value) {
settingsDiskSource.isIconLoadingDisabled = value
}

override val isIconLoadingDisabledFlow: StateFlow<Boolean>
get() = settingsDiskSource
.isIconLoadingDisabledFlow
.map { it ?: false }
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Eagerly,
initialValue = settingsDiskSource
.isIconLoadingDisabled
?: false,
)
override var hasSeenWelcomeTutorial: Boolean
get() = settingsDiskSource.hasSeenWelcomeTutorial
set(value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,49 +58,5 @@ fun NavGraphBuilder.authenticatorGraph(
navController.navigateToEditItem(itemId = it)
}
)
itemListingDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() },
onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() },
onNavigateToEditItemScreen = { navController.navigateToEditItem(itemId = it) },
onNavigateToSyncWithBitwardenScreen = {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT,
)
.show()
/*navController.navigateToSyncWithBitwardenScreen()*/
},
onNavigateToImportScreen = {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT,
)
.show()
/*navController.navigateToImportScreen()*/
},
onNavigateToSearch = { navController.navigateToSearch() },
)
editItemDestination(
onNavigateBack = { navController.popBackStack() },
)
qrCodeScanDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToManualCodeEntryScreen = {
navController.popBackStack()
navController.navigateToManualCodeEntryScreen()
},
)
manualCodeEntryDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToQrCodeScreen = {
navController.popBackStack()
navController.navigateToQrCodeScanScreen()
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.qrcodescan.nav
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 Down Expand Up @@ -59,6 +60,7 @@ fun NavGraphBuilder.itemListingGraph(
navController.navigateToQrCodeScanScreen()
}
)
settingsGraph(navController)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import com.x8bit.bitwarden.authenticator.R
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ITEM_LISTING_GRAPH_ROUTE
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ITEM_LIST_ROUTE
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.itemListingGraph
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.navigateToItemListGraph
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.max
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.toDp
import com.x8bit.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.authenticator.ui.platform.components.scrim.BitwardenAnimatedScrim
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.SETTINGS_GRAPH_ROUTE
import com.x8bit.bitwarden.authenticator.ui.platform.feature.settings.navigateToSettingsGraph
import com.x8bit.bitwarden.authenticator.ui.platform.theme.RootTransitionProviders
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand All @@ -74,14 +77,7 @@ fun AuthenticatorNavBarScreen(
val navOptions = navController.authenticatorNavBarScreenNavOptions()
when (event) {
AuthenticatorNavBarEvent.NavigateToSettings -> {
Toast
.makeText(
navController.context,
R.string.not_yet_implemented,
Toast.LENGTH_SHORT
)
.show()
/* navigateToSettingGraph() */
navigateToSettingsGraph(navOptions)
}

AuthenticatorNavBarEvent.NavigateToVerificationCodes -> {
Expand Down Expand Up @@ -297,7 +293,7 @@ private sealed class AuthenticatorNavBarTab : Parcelable {
override val iconRes get() = R.drawable.ic_verification_codes
override val labelRes get() = R.string.verification_codes
override val contentDescriptionRes get() = R.string.verification_codes
override val route get() = ITEM_LISTING_GRAPH_ROUTE
override val route get() = ITEM_LIST_ROUTE
override val testTag get() = "VerificationCodesTab"
}

Expand All @@ -311,7 +307,7 @@ private sealed class AuthenticatorNavBarTab : Parcelable {
override val labelRes get() = R.string.settings
override val contentDescriptionRes get() = R.string.settings
// TODO: Replace with constant when settings screen is complete.
override val route get() = "settings_graph"
override val route get() = SETTINGS_GRAPH_ROUTE
override val testTag get() = "SettingsTab"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.x8bit.bitwarden.authenticator.ui.platform.components.appbar

import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.tooling.preview.Preview
import com.x8bit.bitwarden.authenticator.R

/**
* A custom Bitwarden-themed medium top app bar with support for actions.
*
* This app bar wraps around Material 3's [MediumTopAppBar] and customizes its appearance
* and behavior according to the app theme.
* It provides a title and an optional set of actions on the trailing side.
* These actions are arranged within a custom action row tailored to the app's design requirements.
*
* @param title The text to be displayed as the title of the app bar.
* @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar
* behaves in conjunction with scrolling content.
* @param actions A lambda containing the set of actions (usually icons or similar) to display
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BitwardenMediumTopAppBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
actions: @Composable RowScope.() -> Unit = {},
) {
MediumTopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,
scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
titleContentColor = MaterialTheme.colorScheme.onSurface,
actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
scrollBehavior = scrollBehavior,
title = {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.semantics { testTag = "PageTitleLabel" },
)
},
modifier = modifier.semantics { testTag = "HeaderBarComponent" },
actions = actions,
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
private fun BitwardenMediumTopAppBar_preview() {
MaterialTheme {
BitwardenMediumTopAppBar(
title = "Preview Title",
scrollBehavior = TopAppBarDefaults
.exitUntilCollapsedScrollBehavior(
rememberTopAppBarState(),
),
actions = {
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.ic_more),
contentDescription = "",
tint = MaterialTheme.colorScheme.onSurface,
)
}
},
)
}
}
Loading

0 comments on commit bf062e5

Please sign in to comment.