Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ fun ActionRequiredCard(
}
}
}
is Account.Status.LoggedIn, is Account.Status.NotConnected.AttemptingToConnect -> Unit
is Account.Status.LoggedIn,
is Account.Status.NotConnected.AttemptingToConnect,
is Account.Status.PasswordChanged -> Unit
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import com.infomaniak.auth.ui.components.Avatar
import com.infomaniak.auth.ui.components.StatusCard
import com.infomaniak.auth.ui.components.StatusCardVariant
import com.infomaniak.auth.ui.previewparameter.fakeAccountPairs
import com.infomaniak.auth.ui.screen.accountlist.AccountListViewModel.AccountListUiState
import com.infomaniak.auth.ui.screen.accountlist.AccountSecurityLevel.Companion.toAccountSecurityLevel
import com.infomaniak.auth.ui.theme.AppDimens.DefaultCornerRadius
import com.infomaniak.auth.ui.theme.AuthenticatorTheme
Expand All @@ -86,6 +87,7 @@ fun AccountListScreen(
uiState = { state },
onAccountClicked = onAccountClicked,
onChallengesRefreshRequested = viewModel::refreshChallenges,
onUserProfilesRefreshRequested = viewModel::refreshUserProfiles,
)
}
is AccountListUiState.Loading -> Unit
Expand All @@ -97,6 +99,7 @@ fun AccountListScreen(
uiState: () -> AccountListUiState.Success,
onAccountClicked: (Account) -> Unit,
onChallengesRefreshRequested: () -> Unit,
onUserProfilesRefreshRequested: () -> Unit,
modifier: Modifier = Modifier
) {
val state = uiState()
Expand All @@ -122,6 +125,7 @@ fun AccountListScreen(
onRefresh = {
isRefreshing = true
onChallengesRefreshRequested()
onUserProfilesRefreshRequested()
},
) {
Column(
Expand Down Expand Up @@ -247,6 +251,7 @@ private fun AccountListScreenPreview() {
uiState = { AccountListUiState.Success(fakeAccountPairs) },
onAccountClicked = {},
onChallengesRefreshRequested = {},
onUserProfilesRefreshRequested = {},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.infomaniak.core.twofactorauth.back.TwoFactorAuthManager
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
Expand All @@ -39,7 +40,7 @@ import javax.inject.Inject

@HiltViewModel
class AccountListViewModel @Inject constructor(
accountUtils: AccountUtils,
private val accountUtils: AccountUtils,
private val authenticatorFacade: AuthenticatorFacade,
private val twoFactorAuthManager: TwoFactorAuthManager
) : ViewModel() {
Expand Down Expand Up @@ -70,10 +71,19 @@ class AccountListViewModel @Inject constructor(
}
}
}
}

@Immutable
sealed interface AccountListUiState {
data object Loading : AccountListUiState
data class Success(val accountPairs: ImmutableList<Pair<Account, User?>>) : AccountListUiState
fun refreshUserProfiles() {
viewModelScope.launch(Dispatchers.IO) {
val userIds = authenticatorFacade.accounts.first().map { it.id.toInt() }.toIntArray()
accountUtils.getUsersById(userIds).forEach { user ->
authenticatorFacade.refreshUserProfileFor(user.apiToken.accessToken, user.id.toLong())
}
}
}

@Immutable
sealed interface AccountListUiState {
data object Loading : AccountListUiState
data class Success(val accountPairs: ImmutableList<Pair<Account, User?>>) : AccountListUiState
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavBackStack
Expand All @@ -40,6 +42,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState
import com.infomaniak.auth.lib.Account
import com.infomaniak.auth.lib.AppStatus
import com.infomaniak.auth.ui.navigation.NavDestination
import com.infomaniak.auth.ui.navigation.baseEntryProvider
Expand Down Expand Up @@ -85,10 +88,17 @@ fun MainScreen(
}
}

var showPasswordDialogFor: Account? by remember { mutableStateOf(null) }

LaunchedEffect(Unit) {
viewModel.accountsWithPasswordUpdate.collect { accounts ->
accounts.firstOrNull()?.let { showPasswordDialogFor = it }
}
}

MainScreen(backStack, entryDecorators)
}


@OptIn(ExperimentalPermissionsApi::class)
private fun handleAppStatus(
appStatus: AppStatus,
Expand All @@ -99,7 +109,10 @@ private fun handleAppStatus(
val targetDestination = when (appStatus) {
is AppStatus.LoginRequired.NotMigrating -> NavDestination.Onboarding.Start
is AppStatus.LoginRequired.MigratingFromLegacyKAuth -> NavDestination.Onboarding.Migration
is AppStatus.LoginRequired.MustReLogin -> NavDestination.LoginInApp(legacyAccountId = appStatus.accountId, isOnboarding = true)
is AppStatus.LoginRequired.MustReLogin -> NavDestination.LoginInApp(
legacyAccountId = appStatus.accountId,
isOnboarding = true
)
is AppStatus.LoggingIn -> NavDestination.SecuringAccount
is AppStatus.EverythingReady -> NavDestination.Onboarding.Complete
is AppStatus.SetupComplete -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class MainViewModel @Inject constructor(
) : ViewModel() {
val appStatus = authenticatorFacade.appStatus

val accountsWithPasswordUpdate = authenticatorFacade.accountsWithUpdatedPassword

val isAppLocked = appSettingsRepository.getSettings().mapNotNull { it?.isAppLockEnabled }
val hasTriggeredNotificationPermission: StateFlow<Boolean> = flow {
emitAll(PermissionPreferences().hasTriggeredNotificationPermissionFlow)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ class AccountUtils @Inject constructor(
suspend fun isUserConnected(): Boolean = users.first().isNotEmpty()

suspend fun getUserById(id: Int): User? = userDao.findById(id)
suspend fun getUsersById(userIds: IntArray): List<User> = userDao.loadAllByIds(userIds)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
package com.infomaniak.auth.utils

import com.infomaniak.auth.lib.models.migration.user.SharedUserProfile
import com.infomaniak.auth.lib.models.migration.user.preferences.Preferences
import com.infomaniak.auth.lib.models.migration.user.preferences.SharedCountry
import com.infomaniak.auth.lib.models.migration.user.preferences.SharedLanguage
import com.infomaniak.auth.lib.models.migration.user.preferences.SharedOrganizationPreference
import com.infomaniak.auth.lib.models.migration.user.preferences.Preferences
import com.infomaniak.auth.lib.models.migration.user.preferences.SharedTimeZone
import com.infomaniak.auth.lib.models.migration.user.preferences.security.SharedAuthDevices
import com.infomaniak.auth.lib.models.migration.user.preferences.security.SharedSecurity
Expand Down Expand Up @@ -60,7 +60,7 @@ fun SharedUserProfile.toUser(): User {
}

private fun Preferences.toCorePreferences() = CorePreferences(
security = security?.toCoreSecurity(),
security = security.toCoreSecurity(),
organizationPreference = organizationPreference.toCoreOrganizationPreference(),
language = language.toCoreLanguage(),
country = country.toCoreCountry(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "ab7e6c6aadc7811b61b0ffe94bc67ec1",
"identityHash": "9a233d24627b386b646a6f9418812e76",
Comment thread
tevincent marked this conversation as resolved.
"entities": [
{
"tableName": "AccountEntity",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `fullName` TEXT NOT NULL, `initials` TEXT NOT NULL, `email` TEXT NOT NULL, `avatarUrl` TEXT, `status` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `fullName` TEXT NOT NULL, `initials` TEXT NOT NULL, `email` TEXT NOT NULL, `avatarUrl` TEXT, `status` INTEGER NOT NULL, `securityScore` INTEGER NOT NULL, `lastPasswordUpdate` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -42,6 +42,17 @@
"columnName": "status",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "securityScore",
"columnName": "securityScore",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastPasswordUpdate",
"columnName": "lastPasswordUpdate",
"affinity": "INTEGER"
}
],
"primaryKey": {
Expand All @@ -54,7 +65,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab7e6c6aadc7811b61b0ffe94bc67ec1')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9a233d24627b386b646a6f9418812e76')"
]
}
}
2 changes: 2 additions & 0 deletions multiplatform-lib/src/commonMain/kotlin/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ data class Account(

data class LoginFailed(val issue: Issue) : NotConnected
}

data class PasswordChanged(val hasBeenHandled: (Unit) -> Unit) : Status
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds
abstract class AuthenticatorFacade internal constructor() {

abstract val accounts: Flow<List<Account>>
abstract val accountsWithUpdatedPassword: Flow<List<Account>>

abstract val appStatus: SharedFlow<AppStatus>

Expand All @@ -62,6 +63,8 @@ abstract class AuthenticatorFacade internal constructor() {
@Throws(Exception::class)
abstract suspend fun refreshTokenFor(userId: Long)

abstract suspend fun refreshUserProfileFor(token: String, userId: Long)

companion object {

fun create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class DummyAuthenticatorFacade internal constructor(
resetAfter: Duration,
) : AuthenticatorFacade() {
override val accounts: Flow<List<Account>>
override val accountsWithUpdatedPassword: Flow<List<Account>>
get() = TODO("Not yet implemented")

private var _accounts: List<Account> by MutableStateFlow<List<Account>>(emptyList()).also {
accounts = accountsRepository.getAccounts().map {
Expand Down Expand Up @@ -119,4 +121,8 @@ class DummyAuthenticatorFacade internal constructor(
override suspend fun refreshTokenFor(userId: Long) {
TODO("Not yet implemented")
}

override suspend fun refreshUserProfileFor(token: String, userId: Long) {
TODO("Not yet implemented")
}
}
Loading
Loading