mirror of
https://github.com/bitwarden/android.git
synced 2026-05-12 23:01:13 -05:00
Compare commits
1 Commits
agalles/te
...
PM-20593-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
509322c972 |
3
.github/renovate.json
vendored
3
.github/renovate.json
vendored
@@ -27,9 +27,6 @@
|
||||
],
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
],
|
||||
"excludePackageNames": [
|
||||
"com.github.bumptech.glide:compose"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Publish Password Manager and Authenticator GitHub Release as newest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 * * * 1-5' # Every hour on the hour on weekdays
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
publish-release-password-manager:
|
||||
name: Publish Password Manager Release
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@agalles/test-1118
|
||||
with:
|
||||
release_name: "Password Manager"
|
||||
workflow_name: "publish-github-release-bwpm.yml"
|
||||
credentials_filename: "play_creds.json"
|
||||
project_type: android
|
||||
check_release_command: >
|
||||
bundle exec fastlane getLatestPlayStoreVersion package_name:com.x8bit.bitwarden track:production
|
||||
secrets: inherit
|
||||
@@ -3,7 +3,7 @@ name: Publish Password Manager and Authenticator GitHub Release as newest
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 * * * 1-5' # Every hour on the hour on weekdays
|
||||
- cron: '0 3 * * 1-5'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -11,12 +11,24 @@ permissions:
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
publish-release-password-manager:
|
||||
name: Publish Password Manager Release
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@main
|
||||
with:
|
||||
release_name: "Password Manager"
|
||||
workflow_name: "publish-github-release.yml"
|
||||
credentials_filename: "play_creds.json"
|
||||
project_type: android
|
||||
check_release_command: >
|
||||
bundle exec fastlane getLatestPlayStoreVersion package_name:com.x8bit.bitwarden track:production
|
||||
secrets: inherit
|
||||
|
||||
publish-release-authenticator:
|
||||
name: Publish Authenticator Release
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@agalles/test-1118
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@main
|
||||
with:
|
||||
release_name: "Authenticator"
|
||||
workflow_name: "publish-github-release-bwa.yml"
|
||||
workflow_name: "publish-github-release.yml"
|
||||
credentials_filename: "authenticator_play_store-creds.json"
|
||||
project_type: android
|
||||
check_release_command: >
|
||||
26
.github/workflows/publish-store.yml
vendored
26
.github/workflows/publish-store.yml
vendored
@@ -1,6 +1,5 @@
|
||||
name: Publish to Google Play
|
||||
run-name: >
|
||||
${{ inputs.dry-run && ' (Dry Run)' || '' }}Promoting ${{ inputs.product }} ${{ inputs.version-code }} from ${{ inputs.track-from }} to ${{ inputs.track-target }}
|
||||
run-name: "Promoting ${{ inputs.product }} ${{ inputs.version-code }} from ${{ inputs.track-from }} to ${{ inputs.track-target }}"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -47,10 +46,6 @@ on:
|
||||
- production
|
||||
- Fastlane Automation Target
|
||||
required: true
|
||||
dry-run:
|
||||
description: "Dry-Run, Run the workflow without publishing to the store"
|
||||
type: boolean
|
||||
default: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
@@ -59,7 +54,6 @@ permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
id-token: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
@@ -128,8 +122,6 @@ jobs:
|
||||
echo "$FORMATTED_MESSAGE" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Promote Play Store version to production
|
||||
if: ${{ inputs.dry-run == false }}
|
||||
id: publish
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEYSTORE-PASSWORD }}
|
||||
PLAY_KEY_PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEY-PASSWORD }}
|
||||
@@ -166,19 +158,3 @@ jobs:
|
||||
releaseNotes:"$RELEASE_NOTES" \
|
||||
track:"$TRACK_FROM" \
|
||||
trackPromoteTo:"$TRACK_TARGET"
|
||||
|
||||
- name: Enable Publish Github Release Workflow
|
||||
if: ${{ steps.publish.conclusion == 'success' || inputs.dry-run }}
|
||||
env:
|
||||
PRODUCT: ${{ inputs.product }}
|
||||
DRY_RUN: ${{ inputs.dry-run }}
|
||||
run: |
|
||||
if $DRY_RUN ; then
|
||||
gh workflow view publish-github-release.yml
|
||||
exit 0
|
||||
fi
|
||||
if [ "$PRODUCT" = "Password Manager" ]; then
|
||||
gh workflow enable publish-github-release-bwpm.yml
|
||||
elif [ "$PRODUCT" = "Authenticator" ]; then
|
||||
gh workflow enable publish-github-release-bwa.yml
|
||||
fi
|
||||
|
||||
@@ -303,8 +303,6 @@ class AuthRepositoryImpl(
|
||||
.syncOrgKeysFlow
|
||||
.onEach { userId ->
|
||||
if (userId == activeUserId) {
|
||||
// TODO: [PM-20593] Investigate why tokens are explicitly refreshed.
|
||||
refreshAccessTokenSynchronously(userId = userId)
|
||||
// We just sync now to get the latest data
|
||||
vaultRepository.sync(forced = true)
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -19,7 +18,7 @@ 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.credentials.providerevents.exception.ImportCredentialsCancellationException
|
||||
import androidx.credentials.providerevents.exception.ImportCredentialsUnknownErrorException
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitwarden.cxf.manager.CredentialExchangeCompletionManager
|
||||
@@ -28,8 +27,7 @@ import com.bitwarden.cxf.ui.composition.LocalCredentialExchangeCompletionManager
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.base.util.toListItemCardStyle
|
||||
import com.bitwarden.ui.platform.components.content.BitwardenEmptyContent
|
||||
import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent
|
||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
@@ -38,7 +36,6 @@ import com.x8bit.bitwarden.ui.vault.feature.exportitems.component.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.exportitems.component.ExportItemsScaffold
|
||||
import com.x8bit.bitwarden.ui.vault.feature.exportitems.model.AccountSelectionListItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.exportitems.selectaccount.handlers.rememberSelectAccountHandlers
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
@@ -46,7 +43,6 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
fun SelectAccountScreen(
|
||||
onAccountSelected: (userId: String) -> Unit,
|
||||
viewModel: SelectAccountViewModel = hiltViewModel(),
|
||||
@@ -61,7 +57,9 @@ fun SelectAccountScreen(
|
||||
credentialExchangeCompletionManager
|
||||
.completeCredentialExport(
|
||||
exportResult = ExportCredentialsResult.Failure(
|
||||
error = ImportCredentialsCancellationException(
|
||||
// TODO: [PM-26094] Use ImportCredentialsCancellationException once
|
||||
// public.
|
||||
error = ImportCredentialsUnknownErrorException(
|
||||
errorMessage = "User cancelled import.",
|
||||
),
|
||||
),
|
||||
@@ -84,40 +82,19 @@ fun SelectAccountScreen(
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) {
|
||||
when (val viewState = state.viewState) {
|
||||
is SelectAccountState.ViewState.Content -> {
|
||||
SelectAccountContent(
|
||||
accountSelectionListItems = viewState.accountSelectionListItems,
|
||||
onAccountClick = handlers.onAccountClick,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
SelectAccountState.ViewState.Loading -> {
|
||||
BitwardenLoadingContent(
|
||||
text = stringResource(BitwardenString.loading),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
SelectAccountState.ViewState.NoItems -> {
|
||||
BitwardenEmptyContent(
|
||||
title = stringResource(BitwardenString.no_accounts_available),
|
||||
titleTestTag = "NoAccountsTitle",
|
||||
text = stringResource(
|
||||
BitwardenString.you_dont_have_any_accounts_you_can_import_from,
|
||||
),
|
||||
labelTestTag = "NoAccountsText",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
SelectAccountContent(
|
||||
state = state,
|
||||
onAccountClick = handlers.onAccountClick,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SelectAccountContent(
|
||||
accountSelectionListItems: ImmutableList<AccountSelectionListItem>,
|
||||
state: SelectAccountState,
|
||||
onAccountClick: (userId: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -129,121 +106,62 @@ private fun SelectAccountContent(
|
||||
text = stringResource(BitwardenString.select_account),
|
||||
textAlign = TextAlign.Center,
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
item { Spacer(Modifier.height(16.dp)) }
|
||||
|
||||
itemsIndexed(
|
||||
items = accountSelectionListItems,
|
||||
items = state.accountSelectionListItems,
|
||||
key = { _, item -> "AccountSummaryItem_${item.userId}" },
|
||||
) { index, item ->
|
||||
AccountSummaryListItem(
|
||||
item = item,
|
||||
cardStyle = accountSelectionListItems.toListItemCardStyle(index),
|
||||
cardStyle = state.accountSelectionListItems.toListItemCardStyle(index),
|
||||
clickable = true,
|
||||
onClick = onAccountClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.animateItem(),
|
||||
)
|
||||
}
|
||||
item { Spacer(Modifier.height(16.dp)) }
|
||||
item { Spacer(Modifier.navigationBarsPadding()) }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
name = "Select account content",
|
||||
showSystemUi = true,
|
||||
)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SelectAccountContent_preview() {
|
||||
ExportItemsScaffold(
|
||||
navIcon = rememberVectorPainter(BitwardenDrawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(BitwardenString.close),
|
||||
onNavigationIconClick = { },
|
||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||
) {
|
||||
private fun SelectAccountContentPreview() {
|
||||
val state = SelectAccountState(
|
||||
accountSelectionListItems = persistentListOf(
|
||||
AccountSelectionListItem(
|
||||
userId = "1",
|
||||
email = "john.doe@example.com",
|
||||
initials = "JD",
|
||||
avatarColorHex = "#FFFF0000",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = "2",
|
||||
email = "jane.smith@example.com",
|
||||
initials = "JS",
|
||||
avatarColorHex = "#FF00FF00",
|
||||
isItemRestricted = true,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = "3",
|
||||
email = "another.user@example.com",
|
||||
initials = "AU",
|
||||
avatarColorHex = "#FF0000FF",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
BitwardenScaffold {
|
||||
SelectAccountContent(
|
||||
accountSelectionListItems = persistentListOf(
|
||||
AccountSelectionListItem(
|
||||
userId = "1",
|
||||
email = "john.doe@example.com",
|
||||
initials = "JD",
|
||||
avatarColorHex = "#FFFF0000",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = "2",
|
||||
email = "jane.smith@example.com",
|
||||
initials = "JS",
|
||||
avatarColorHex = "#FF00FF00",
|
||||
isItemRestricted = true,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = "3",
|
||||
email = "another.user@example.com",
|
||||
initials = "AU",
|
||||
avatarColorHex = "#FF0000FF",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
),
|
||||
state = state,
|
||||
onAccountClick = { },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
name = "No accounts content",
|
||||
showSystemUi = true,
|
||||
)
|
||||
@Composable
|
||||
private fun NoAccountsContent_preview() {
|
||||
ExportItemsScaffold(
|
||||
navIcon = rememberVectorPainter(BitwardenDrawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(BitwardenString.close),
|
||||
onNavigationIconClick = { },
|
||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||
) {
|
||||
BitwardenEmptyContent(
|
||||
title = stringResource(BitwardenString.no_accounts_available),
|
||||
titleTestTag = "NoAccountsTitle",
|
||||
text = stringResource(
|
||||
BitwardenString.you_dont_have_any_accounts_you_can_import_from,
|
||||
),
|
||||
labelTestTag = "NoAccountsText",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
name = "Loading content",
|
||||
showSystemUi = true,
|
||||
)
|
||||
@Composable
|
||||
private fun LoadingContent_preview() {
|
||||
ExportItemsScaffold(
|
||||
navIcon = rememberVectorPainter(BitwardenDrawable.ic_close),
|
||||
navigationIconContentDescription = stringResource(BitwardenString.close),
|
||||
onNavigationIconClick = { },
|
||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||
) {
|
||||
BitwardenLoadingContent(
|
||||
text = stringResource(BitwardenString.loading),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.exportitems.selectaccount
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.network.model.PolicyTypeJson
|
||||
import com.bitwarden.network.model.SyncResponseJson
|
||||
@@ -12,13 +11,12 @@ import com.x8bit.bitwarden.ui.vault.feature.exportitems.model.AccountSelectionLi
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -30,7 +28,7 @@ class SelectAccountViewModel @Inject constructor(
|
||||
policyManager: PolicyManager,
|
||||
) : BaseViewModel<SelectAccountState, SelectAccountEvent, SelectAccountAction>(
|
||||
initialState = SelectAccountState(
|
||||
viewState = SelectAccountState.ViewState.Loading,
|
||||
accountSelectionListItems = persistentListOf(),
|
||||
),
|
||||
) {
|
||||
|
||||
@@ -97,38 +95,30 @@ class SelectAccountViewModel @Inject constructor(
|
||||
.filter { it.isEnabled }
|
||||
.map { it.organizationId }
|
||||
|
||||
val accountSelectionListItems = action.userState
|
||||
?.accounts
|
||||
.orEmpty()
|
||||
// We only want accounts that do not restrict personal vault ownership
|
||||
.filter { account ->
|
||||
account
|
||||
.organizations
|
||||
.none { org -> org.id in personalOwnershipRestrictedOrgIds }
|
||||
}
|
||||
.map { account ->
|
||||
AccountSelectionListItem(
|
||||
userId = account.userId,
|
||||
email = account.email,
|
||||
initials = account.initials,
|
||||
avatarColorHex = account.avatarColorHex,
|
||||
// Indicate which accounts have item restrictions applied.
|
||||
isItemRestricted = account
|
||||
.organizations
|
||||
.any { org -> org.id in itemRestrictedOrgIds },
|
||||
)
|
||||
}
|
||||
.toImmutableList()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = if (accountSelectionListItems.isEmpty()) {
|
||||
SelectAccountState.ViewState.NoItems
|
||||
} else {
|
||||
SelectAccountState.ViewState.Content(
|
||||
accountSelectionListItems = accountSelectionListItems,
|
||||
)
|
||||
},
|
||||
accountSelectionListItems = action.userState
|
||||
?.accounts
|
||||
.orEmpty()
|
||||
// We only want accounts that do not restrict personal vault ownership
|
||||
.filter { account ->
|
||||
account
|
||||
.organizations
|
||||
.none { org -> org.id in personalOwnershipRestrictedOrgIds }
|
||||
}
|
||||
.map { account ->
|
||||
AccountSelectionListItem(
|
||||
userId = account.userId,
|
||||
email = account.email,
|
||||
initials = account.initials,
|
||||
avatarColorHex = account.avatarColorHex,
|
||||
// Indicate which accounts have item restrictions applied.
|
||||
isItemRestricted = account
|
||||
.organizations
|
||||
.any { org -> org.id in itemRestrictedOrgIds },
|
||||
)
|
||||
}
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,40 +126,12 @@ class SelectAccountViewModel @Inject constructor(
|
||||
|
||||
/**
|
||||
* Represents the state for the select account screen.
|
||||
*
|
||||
* @param accountSelectionListItems The list of account summaries to be displayed for selection.
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
data class SelectAccountState(
|
||||
val viewState: ViewState,
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Represents the different states for the select account screen.
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
sealed class ViewState : Parcelable {
|
||||
/**
|
||||
* Represents the loading state for the select account screen.
|
||||
*/
|
||||
data object Loading : ViewState()
|
||||
|
||||
/**
|
||||
* Represents the content state for the select account screen.
|
||||
*
|
||||
* @param accountSelectionListItems The list of account summaries to be displayed for
|
||||
* selection.
|
||||
*/
|
||||
data class Content(
|
||||
val accountSelectionListItems: ImmutableList<AccountSelectionListItem>,
|
||||
) : ViewState()
|
||||
|
||||
/**
|
||||
* Represents the no items state for the select account screen.
|
||||
*/
|
||||
data object NoItems : ViewState()
|
||||
}
|
||||
}
|
||||
val accountSelectionListItems: ImmutableList<AccountSelectionListItem>,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the actions that can be performed on the select account screen.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.exportitems
|
||||
|
||||
import androidx.compose.ui.test.isDisplayed
|
||||
import androidx.compose.ui.test.isNotDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
@@ -125,42 +124,6 @@ class SelectAccountScreenTest : BitwardenComposeTest() {
|
||||
|
||||
assertTrue(onAccountSelectedCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NoItemsContent should be displayed according to state`() = runTest {
|
||||
mockkStateFlow.emit(
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = SelectAccountState.ViewState.NoItems,
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("No accounts available")
|
||||
.isDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(
|
||||
text = "You don't have any accounts you can import from.",
|
||||
substring = true,
|
||||
)
|
||||
.isDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Select an account")
|
||||
.isNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Loading content should be displayed according to state`() = runTest {
|
||||
mockkStateFlow.emit(
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = SelectAccountState.ViewState.Loading,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithText("Loading")
|
||||
.isDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(
|
||||
@@ -185,22 +148,20 @@ private val LOCKED_ACCOUNT_SUMMARY = AccountSummary(
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE = SelectAccountState(
|
||||
viewState = SelectAccountState.ViewState.Content(
|
||||
accountSelectionListItems = persistentListOf(
|
||||
AccountSelectionListItem(
|
||||
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
||||
email = ACTIVE_ACCOUNT_SUMMARY.email,
|
||||
initials = "AA",
|
||||
avatarColorHex = "#FFFF0000",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = LOCKED_ACCOUNT_SUMMARY.userId,
|
||||
email = LOCKED_ACCOUNT_SUMMARY.email,
|
||||
initials = "LU",
|
||||
avatarColorHex = "#FF00FF00",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
accountSelectionListItems = persistentListOf(
|
||||
AccountSelectionListItem(
|
||||
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
||||
email = ACTIVE_ACCOUNT_SUMMARY.email,
|
||||
initials = "AA",
|
||||
avatarColorHex = "#FFFF0000",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
AccountSelectionListItem(
|
||||
userId = LOCKED_ACCOUNT_SUMMARY.userId,
|
||||
email = LOCKED_ACCOUNT_SUMMARY.email,
|
||||
initials = "LU",
|
||||
avatarColorHex = "#FF00FF00",
|
||||
isItemRestricted = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -50,8 +50,11 @@ class SelectAccountViewModelTest : BaseViewModelTest() {
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
assertEquals(
|
||||
SelectAccountState(viewState = SelectAccountState.ViewState.Loading),
|
||||
SelectAccountState(
|
||||
accountSelectionListItems = persistentListOf(),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
@@ -76,11 +79,7 @@ class SelectAccountViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
SelectAccountState(
|
||||
viewState = SelectAccountState.ViewState.Content(
|
||||
accountSelectionListItems = persistentListOf(expectedItem),
|
||||
),
|
||||
),
|
||||
SelectAccountState(accountSelectionListItems = persistentListOf(expectedItem)),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
@@ -117,7 +116,9 @@ class SelectAccountViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SelectAccountState(viewState = SelectAccountState.ViewState.NoItems),
|
||||
SelectAccountState(
|
||||
accountSelectionListItems = persistentListOf(),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
@@ -162,11 +163,7 @@ class SelectAccountViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
SelectAccountState(
|
||||
viewState = SelectAccountState.ViewState.Content(
|
||||
accountSelectionListItems = persistentListOf(expectedItem),
|
||||
),
|
||||
),
|
||||
SelectAccountState(accountSelectionListItems = persistentListOf(expectedItem)),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ mockk = "1.14.5"
|
||||
okhttp = "5.1.0"
|
||||
retrofitBom = "3.0.0"
|
||||
robolectric = "4.16"
|
||||
sonarqube = "6.3.1.5724"
|
||||
sonarqube = "6.2.0.5505"
|
||||
testng = "7.11.0"
|
||||
timber = "5.0.1"
|
||||
turbine = "1.2.1"
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bitwarden.network.api
|
||||
import com.bitwarden.network.model.NetworkResult
|
||||
import com.bitwarden.network.model.SyncResponseJson
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
|
||||
/**
|
||||
* This interface defines the API service for fetching vault data.
|
||||
@@ -13,6 +14,7 @@ internal interface SyncApi {
|
||||
*
|
||||
* @return A [SyncResponseJson] containing the vault response model.
|
||||
*/
|
||||
@Headers("Access-Token-Sync: true")
|
||||
@GET("sync")
|
||||
suspend fun sync(): NetworkResult<SyncResponseJson>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
private const val AUTH_TOKEN_SYNC_HEADER: String = "Access-Token-Sync"
|
||||
private const val MISSING_TOKEN_MESSAGE: String = "Auth token is missing!"
|
||||
private const val MISSING_PROVIDER_MESSAGE: String = "Refresh token provider is missing!"
|
||||
private const val EXPIRATION_OFFSET_MINUTES: Long = 5L
|
||||
@@ -79,7 +80,9 @@ internal class AuthTokenManager(
|
||||
val expirationTime = Instant
|
||||
.ofEpochSecond(tokenData.expiresAtSec)
|
||||
.minus(EXPIRATION_OFFSET_MINUTES, ChronoUnit.MINUTES)
|
||||
if (clock.instant().isAfter(expirationTime)) {
|
||||
if (clock.instant().isAfter(expirationTime) ||
|
||||
chain.request().header(AUTH_TOKEN_SYNC_HEADER).toBoolean()
|
||||
) {
|
||||
Timber.d("Attempting to refresh token due to expiration")
|
||||
refreshTokenProvider
|
||||
?.refreshAccessTokenSynchronously(userId = tokenData.userId)
|
||||
@@ -92,6 +95,7 @@ internal class AuthTokenManager(
|
||||
val request = chain
|
||||
.request()
|
||||
.newBuilder()
|
||||
.removeHeader(AUTH_TOKEN_SYNC_HEADER)
|
||||
.addHeader(
|
||||
name = HEADER_KEY_AUTHORIZATION,
|
||||
value = "${HEADER_VALUE_BEARER_PREFIX}$token",
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.bitwarden.ui.platform.components.content
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
@@ -13,14 +12,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.base.util.nullableTestTag
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.components.icon.BitwardenIcon
|
||||
import com.bitwarden.ui.platform.components.icon.model.IconData
|
||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -32,8 +28,6 @@ fun BitwardenEmptyContent(
|
||||
modifier: Modifier = Modifier,
|
||||
illustrationData: IconData? = null,
|
||||
labelTestTag: String? = null,
|
||||
title: String? = null,
|
||||
titleTestTag: String? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
@@ -47,19 +41,6 @@ fun BitwardenEmptyContent(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
}
|
||||
title?.let {
|
||||
Text(
|
||||
text = title,
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.nullableTestTag(tag = titleTestTag),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
@@ -73,20 +54,3 @@ fun BitwardenEmptyContent(
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, name = "Bitwarden empty content")
|
||||
@Composable
|
||||
private fun BitwardenEmptyContent_preview() {
|
||||
BitwardenScaffold {
|
||||
BitwardenEmptyContent(
|
||||
title = "Empty content",
|
||||
titleTestTag = "TitleTestTag",
|
||||
text = "There is no content to display",
|
||||
labelTestTag = "EmptyContentLabel",
|
||||
illustrationData = IconData.Local(BitwardenDrawable.ic_empty_vault),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.bitwarden.ui.platform.components.content
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -12,11 +11,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.components.indicator.BitwardenCircularProgressIndicator
|
||||
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -50,16 +46,3 @@ fun BitwardenLoadingContent(
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, name = "Bitwarden loading content")
|
||||
@Composable
|
||||
private fun BitwardenLoadingContent_preview() {
|
||||
BitwardenScaffold {
|
||||
BitwardenLoadingContent(
|
||||
text = "Loading...",
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1119,8 +1119,6 @@ Do you want to switch to this account?</string>
|
||||
<string name="not_now">Not now</string>
|
||||
<string name="import_from_bitwarden">Import from Bitwarden</string>
|
||||
<string name="select_account">Select account</string>
|
||||
<string name="no_accounts_available">No accounts available</string>
|
||||
<string name="you_dont_have_any_accounts_you_can_import_from">You don’t have any accounts you can import from. Your organization’s security policy may restrict importing items from Bitwarden to another app.</string>
|
||||
<string name="import_restricted_unable_to_import_credit_cards">Import restricted, unable to import cards from this account.</string>
|
||||
<string name="verify_your_master_password">Verify your master password</string>
|
||||
<string name="having_trouble_with_autofill">Having trouble with autofill?</string>
|
||||
|
||||
Reference in New Issue
Block a user