Skip to content

Commit

Permalink
[PM-12595] add notification badge to settings item in settings tab sc…
Browse files Browse the repository at this point in the history
…reen (#3976)
  • Loading branch information
dseverns-livefront authored Sep 27, 2024
1 parent 70e75b9 commit 853f25b
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.x8bit.bitwarden.ui.platform.feature.settings

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -20,6 +23,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.ripple
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
Expand All @@ -29,12 +33,14 @@ import androidx.compose.ui.res.stringResource
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.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
import com.x8bit.bitwarden.ui.platform.components.badge.NotificationBadge
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
Expand All @@ -53,6 +59,7 @@ fun SettingsScreen(
onNavigateToVault: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel(),
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
EventsEffect(viewModel = viewModel) { event ->
when (event) {
SettingsEvent.NavigateAbout -> onNavigateToAbout()
Expand Down Expand Up @@ -82,16 +89,20 @@ fun SettingsScreen(
.fillMaxSize()
.verticalScroll(state = rememberScrollState()),
) {
Settings.entries.forEach {
Settings.entries.forEach { settingEntry ->
SettingsRow(
text = it.text,
text = settingEntry.text,
onClick = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.SettingsClick(it)) }
{ viewModel.trySendAction(SettingsAction.SettingsClick(settingEntry)) }
},
modifier = Modifier
.testTag(it.testTag)
.testTag(settingEntry.testTag)
.padding(horizontal = 16.dp)
.fillMaxWidth(),
notificationCount = state.notificationBadgeCountMap.getOrDefault(
key = settingEntry,
defaultValue = 0,
),
)
}
}
Expand All @@ -103,6 +114,7 @@ private fun SettingsRow(
text: Text,
onClick: () -> Unit,
modifier: Modifier = Modifier,
notificationCount: Int,
) {
Row(
modifier = Modifier
Expand All @@ -126,6 +138,27 @@ private fun SettingsRow(
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
)
TrailingContent(notificationCount = notificationCount)
}
}

@Composable
private fun TrailingContent(
notificationCount: Int,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
) {
val notificationBadgeVisible = notificationCount > 0
NotificationBadge(
notificationCount = notificationCount,
isVisible = notificationBadgeVisible,
)
if (notificationBadgeVisible) {
Spacer(modifier = Modifier.width(12.dp))
}
Icon(
painter = rememberVectorPainter(id = R.drawable.ic_navigate_next),
contentDescription = null,
Expand All @@ -138,18 +171,21 @@ private fun SettingsRow(
}

@Preview
@Preview(name = "Right-To-Left", locale = "ar")
@Composable
private fun SettingsRows_preview() {
BitwardenTheme {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
.fillMaxSize(),
) {
Settings.entries.forEach {
Settings.entries.forEachIndexed { index, it ->
SettingsRow(
text = it.text,
onClick = { },
notificationCount = index % 3,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,62 @@
package com.x8bit.bitwarden.ui.platform.feature.settings

import androidx.compose.material3.Text
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import javax.inject.Inject

/**
* View model for the settings screen.
*/
@HiltViewModel
class SettingsViewModel @Inject constructor() : BaseViewModel<Unit, SettingsEvent, SettingsAction>(
initialState = Unit,
class SettingsViewModel @Inject constructor(
settingsRepository: SettingsRepository,
) : BaseViewModel<SettingsState, SettingsEvent, SettingsAction>(
initialState = SettingsState(
securityCount = settingsRepository.allSecuritySettingsBadgeCountFlow.value,
autoFillCount = settingsRepository.allAutofillSettingsBadgeCountFlow.value,
),
) {

init {
combine(
settingsRepository.allSecuritySettingsBadgeCountFlow,
settingsRepository.allAutofillSettingsBadgeCountFlow,
) { securityCount, autofillCount ->
SettingsAction.Internal.SettingsNotificationCountUpdate(
securityCount = securityCount,
autoFillCount = autofillCount,
)
}
.onEach(::sendAction)
.launchIn(viewModelScope)
}

override fun handleAction(action: SettingsAction): Unit = when (action) {
is SettingsAction.SettingsClick -> handleAccountSecurityClick(action)
is SettingsAction.Internal.SettingsNotificationCountUpdate -> {
handleSettingsNotificationCountUpdate(action)
}
}

private fun handleSettingsNotificationCountUpdate(
action: SettingsAction.Internal.SettingsNotificationCountUpdate,
) {
mutableStateFlow.update {
it.copy(
autoFillCount = action.autoFillCount,
securityCount = action.securityCount,
)
}
}

private fun handleAccountSecurityClick(action: SettingsAction.SettingsClick) {
Expand Down Expand Up @@ -48,6 +88,19 @@ class SettingsViewModel @Inject constructor() : BaseViewModel<Unit, SettingsEven
}
}

/**
* Models the state of the settings screen.
*/
data class SettingsState(
private val autoFillCount: Int,
private val securityCount: Int,
) {
val notificationBadgeCountMap: Map<Settings, Int> = mapOf(
Settings.ACCOUNT_SECURITY to autoFillCount,
Settings.AUTO_FILL to securityCount,
)
}

/**
* Models events for the settings screen.
*/
Expand Down Expand Up @@ -93,6 +146,19 @@ sealed class SettingsAction {
data class SettingsClick(
val settings: Settings,
) : SettingsAction()

/**
* Models internal actions for the settings screen.
*/
sealed class Internal : SettingsAction() {
/**
* Update the notification count for the settings rows.
*/
data class SettingsNotificationCountUpdate(
val autoFillCount: Int,
val securityCount: Int,
) : Internal()
}
}

/**
Expand Down
Loading

0 comments on commit 853f25b

Please sign in to comment.