Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
737108f
feat: Add ic_circle_information_outlined drawable
baillyjamy May 13, 2026
5ab5b99
chore: Add disconnectCriticalButton string resource
baillyjamy May 13, 2026
9092df2
chore: Add disconnectAccountPartiallySecured string resources
baillyjamy May 13, 2026
383dd80
chore: Add disconnectAccountAdd2faButton string resource
baillyjamy May 13, 2026
dfc84ce
chore: Add removeAccountWarningTitle and removeAccountWarningDescript…
baillyjamy May 13, 2026
33d4377
chore: Add removeAccountButton string resource
baillyjamy May 13, 2026
6b23498
feat: Add disconnect account dialog flow
baillyjamy May 13, 2026
6389baf
Merge remote-tracking branch 'origin/main' into feat-modal-disconnect
baillyjamy May 13, 2026
86276d4
Merge remote-tracking branch 'origin/feat-security-status' into feat-…
baillyjamy May 13, 2026
a593e00
feat: Remove account without user
baillyjamy May 13, 2026
15b3433
Merge remote-tracking branch 'origin/main' into feat-modal-disconnect
baillyjamy May 13, 2026
fc733f3
feat: Move Dialog package
baillyjamy May 13, 2026
26ba080
feat: Use viewmodel in disconnect dialog
baillyjamy May 18, 2026
da9978a
refactor: Rename DisconnectViewModel to DisconnectDialogViewModel
baillyjamy May 18, 2026
16fd8e1
fix: Remove useless dialog property
baillyjamy May 18, 2026
7f4c6a6
fix: Remove useless @Serializable
baillyjamy May 18, 2026
bdf7c0a
Merge remote-tracking branch 'origin/main' into feat-modal-disconnect
baillyjamy May 18, 2026
c5361ba
fix: Apply copilote suggestions
baillyjamy May 18, 2026
e0fa9b0
fix: Remove useless accountRemovedChannel
baillyjamy May 18, 2026
84c5821
chore: Remove useless Channel
tevincent May 18, 2026
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 @@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.auth.ui.components.dialog
package com.infomaniak.auth.ui.dialog

import android.annotation.SuppressLint
import androidx.compose.material3.AlertDialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.auth.ui.components.dialog
package com.infomaniak.auth.ui.dialog

import android.annotation.SuppressLint
import androidx.compose.material3.AlertDialog
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Infomaniak Authenticator - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.auth.ui.dialog.disconnect

import android.annotation.SuppressLint
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.infomaniak.auth.R
import com.infomaniak.auth.ui.screen.accountdetails.DisconnectConfiguration
import com.infomaniak.auth.ui.theme.AuthenticatorTheme
import com.infomaniak.core.ui.compose.preview.PreviewSmallWindow

@Composable
fun DisconnectConfirmDialog(
accountId: Long,
configuration: DisconnectConfiguration,
onAccountDisconnected: () -> Unit,
onDismissRequest: () -> Unit,
viewModel: DisconnectDialogViewModel = hiltViewModel()
) {
LaunchedEffect(Unit) {
viewModel.fetchAccountDetails(accountId)
}

AlertDialog(
icon = {
Icon(
painter = painterResource(R.drawable.triangle_alert),
contentDescription = null
)
},
onDismissRequest = onDismissRequest,
title = {
Text(
text = stringResource(configuration.confirmationTitleResId),
textAlign = TextAlign.Center
)
},
text = {
Text(text = stringResource(configuration.confirmationDescriptionResId))
},
confirmButton = {
TextButton(
onClick = {
viewModel.removeAccount(onAccountRemoved = onAccountDisconnected)
},
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.error,
)
) {
Text(stringResource(configuration.criticalButtonStringResId))
}
}
)
}

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@PreviewSmallWindow
@Composable
private fun DisconnectWarningDialogPreview() {
AuthenticatorTheme {
Scaffold { _ ->
DisconnectConfirmDialog(
accountId = 1L,
configuration = DisconnectConfiguration.DisconnectSecuredAccount,
onAccountDisconnected = {},
onDismissRequest = {},
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Infomaniak Authenticator - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.auth.ui.dialog.disconnect

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.infomaniak.auth.lib.AuthenticatorFacade
import com.infomaniak.auth.utils.AccountUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class DisconnectDialogViewModel @Inject constructor(
private val accountUtils: AccountUtils,
private val authenticatorFacade: AuthenticatorFacade,
) : ViewModel() {
private val accountIdFlow = MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)

val userAccessToken = accountIdFlow
.flatMapLatest { id ->
combine(
accountUtils.users,
authenticatorFacade.accounts
) { users, accounts ->
val user = users.find { it.id.toLong() == id }
val account = accounts.find { it.id == id }

if (account != null) user?.apiToken?.accessToken else null
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = null
)

fun fetchAccountDetails(accountId: Long) {
accountIdFlow.tryEmit(accountId)
}

fun removeAccount(onAccountRemoved: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
accountIdFlow.first().let { accountId ->
accountUtils.removeUser(accountId.toInt())
authenticatorFacade.removeAccount(token = null, id = accountId)
Comment thread
baillyjamy marked this conversation as resolved.
onAccountRemoved()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Infomaniak Authenticator - Android
* Copyright (C) 2026 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.auth.ui.dialog.disconnect

import android.annotation.SuppressLint
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.infomaniak.auth.R
import com.infomaniak.auth.lib.models.UrlConstants
import com.infomaniak.auth.ui.screen.accountdetails.DisconnectConfiguration
import com.infomaniak.auth.ui.theme.AuthenticatorTheme
import com.infomaniak.core.network.ApiEnvironment
import com.infomaniak.core.ui.compose.preview.PreviewSmallWindow
import com.infomaniak.core.webview.ui.WebViewActivity

@Composable
fun DisconnectWarningDialog(
accountId: Long,
configuration: DisconnectConfiguration,
onDismissRequest: () -> Unit,
onConfirmButton: () -> Unit,
viewModel: DisconnectDialogViewModel = hiltViewModel()
) {
val context = LocalContext.current
val host = ApiEnvironment.current.host

val userAccessToken by viewModel.userAccessToken.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
viewModel.fetchAccountDetails(accountId)
}

AlertDialog(
icon = {
Icon(
painter = painterResource(R.drawable.ic_circle_information_outlined),
contentDescription = null
)
},
onDismissRequest = onDismissRequest,
title = {
Text(
text = stringResource(configuration.warningTitleResId),
textAlign = TextAlign.Center
)
},
text = {
Text(text = stringResource(configuration.warningDescriptionResId))
},
confirmButton = {
TextButton(
onClick = onConfirmButton,
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.error,
)
) {
Text(stringResource(configuration.criticalButtonStringResId))
}
},
dismissButton = {
if (userAccessToken != null) {
TextButton(
onClick = {
onDismissRequest()
WebViewActivity.startActivity(
context = context,
url = UrlConstants.autologUrl(
host = host,
UrlConstants.managerUrl(host, configuration.dismissHelpUrl)
),
headers = mapOf("Authorization" to "Bearer $userAccessToken"),
)
}
) {
Text(stringResource(configuration.neutralButtonStringResId))
}
}
}
)
}

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@PreviewSmallWindow
@Composable
private fun DisconnectWarningDialogPreview() {
AuthenticatorTheme {
Scaffold { _ ->
DisconnectWarningDialog(
accountId = 1L,
configuration = DisconnectConfiguration.DisconnectSecuredAccount,
onDismissRequest = {},
onConfirmButton = {}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.infomaniak.auth.ui.navigation

import androidx.compose.runtime.Immutable
import androidx.navigation3.runtime.NavKey
import com.infomaniak.auth.ui.screen.accountdetails.DisconnectConfiguration
import kotlinx.serialization.Serializable

@Immutable
Expand Down Expand Up @@ -85,6 +86,20 @@ sealed interface NavDestination : NavKey {

@Serializable
data class AccountDetails(val accountId: Long) : NavDestination

//region Dialog

interface DialogDestination : NavDestination

sealed interface DisconnectDialog : DialogDestination {
@Serializable
data class DisconnectWarning(val accountId: Long, val configuration: DisconnectConfiguration) : DisconnectDialog

@Serializable
data class DisconnectConfirmation(val accountId: Long, val configuration: DisconnectConfiguration) : DisconnectDialog
}
Comment thread
baillyjamy marked this conversation as resolved.

//endregion
}

@Immutable
Expand Down
Loading
Loading