diff --git a/Core b/Core
index bcde437d59b..6abfe6a48ac 160000
--- a/Core
+++ b/Core
@@ -1 +1 @@
-Subproject commit bcde437d59bd1f46354603bb12b1fda724f405f1
+Subproject commit 6abfe6a48ac25bd5c9027a02aa45077f4472f0da
diff --git a/LICENSES.md b/LICENSES.md
index 14f28302105..ac9d80ab475 100644
--- a/LICENSES.md
+++ b/LICENSES.md
@@ -1,40 +1,42 @@
# Open Source Licenses
-Infomaniak Mail for Android includes the following notable open source libraries (runtime dependencies; test-only dependencies are excluded). This list may not be exhaustive.
+Infomaniak Mail for Android includes the following notable open source libraries (runtime dependencies; test-only dependencies are
+excluded). This list may not be exhaustive.
-| Library | License |
-|---------|---------|
-| [AndroidX Activity Compose](https://developer.android.com/jetpack/androidx/releases/activity) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX AppCompat](https://developer.android.com/jetpack/androidx/releases/appcompat) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Concurrent Futures](https://developer.android.com/jetpack/androidx/releases/concurrent) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Core KTX](https://developer.android.com/jetpack/androidx/releases/core) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX DataStore Preferences](https://developer.android.com/jetpack/androidx/releases/datastore) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Emoji2](https://developer.android.com/jetpack/androidx/releases/emoji2) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Navigation](https://developer.android.com/jetpack/androidx/releases/navigation) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX RecyclerView](https://developer.android.com/jetpack/androidx/releases/recyclerview) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Room](https://developer.android.com/jetpack/androidx/releases/room) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX WebKit](https://developer.android.com/jetpack/androidx/releases/webkit) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [AndroidX Work Runtime](https://developer.android.com/jetpack/androidx/releases/work) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Coil](https://github.com/coil-kt/coil) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Dagger Hilt](https://dagger.dev/hilt/) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [DragDropSwipe RecyclerView](https://github.com/ernestoyaquello/DragDropSwipeRecyclerview) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) _(standard flavor only)_ | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [FlexboxLayout](https://github.com/google/flexbox-layout) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Google Material Components for Android](https://github.com/material-components/material-components-android) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Google Play App Update](https://developer.android.com/guide/playcore/in-app-updates) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Google Play Review](https://developer.android.com/guide/playcore/in-app-review) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Google Play Services](https://developers.google.com/android/guides/overview) _(standard flavor only)_ | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Infomaniak Rich HTML Editor](https://github.com/Infomaniak/android-rich-html-editor) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Jetpack Compose](https://developer.android.com/jetpack/compose) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Kotlin Standard Library](https://kotlinlang.org/) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Kotlinx Coroutines](https://github.com/Kotlin/kotlinx.coroutines) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Lottie](https://github.com/airbnb/lottie-android) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Matomo SDK for Android](https://github.com/matomo-org/matomo-sdk-android) | [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause) |
-| [OkHttp](https://github.com/square/okhttp) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Okio](https://github.com/square/okio) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Realm Kotlin](https://github.com/realm/realm-kotlin) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [Sentry Android SDK](https://github.com/getsentry/sentry-java) | [MIT License](https://opensource.org/licenses/MIT) |
-| [Splitties](https://github.com/LouisCAD/Splitties) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
-| [ViewPager Dots Indicator](https://github.com/tommybuonomo/dotsindicator) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| Library | License |
+|--------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
+| [AndroidX Activity Compose](https://developer.android.com/jetpack/androidx/releases/activity) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX AppCompat](https://developer.android.com/jetpack/androidx/releases/appcompat) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Concurrent Futures](https://developer.android.com/jetpack/androidx/releases/concurrent) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Core KTX](https://developer.android.com/jetpack/androidx/releases/core) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX DataStore Preferences](https://developer.android.com/jetpack/androidx/releases/datastore) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Emoji2](https://developer.android.com/jetpack/androidx/releases/emoji2) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Navigation](https://developer.android.com/jetpack/androidx/releases/navigation) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX RecyclerView](https://developer.android.com/jetpack/androidx/releases/recyclerview) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Room](https://developer.android.com/jetpack/androidx/releases/room) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX WebKit](https://developer.android.com/jetpack/androidx/releases/webkit) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [AndroidX Work Runtime](https://developer.android.com/jetpack/androidx/releases/work) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Coil](https://github.com/coil-kt/coil) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Dagger Hilt](https://dagger.dev/hilt/) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [DragDropSwipe RecyclerView](https://github.com/ernestoyaquello/DragDropSwipeRecyclerview) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) _(standard flavor only)_ | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [FlexboxLayout](https://github.com/google/flexbox-layout) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Google Material Components for Android](https://github.com/material-components/material-components-android) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Google Play App Update](https://developer.android.com/guide/playcore/in-app-updates) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Google Play Review](https://developer.android.com/guide/playcore/in-app-review) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Google Play Services](https://developers.google.com/android/guides/overview) _(standard flavor only)_ | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Infomaniak Rich HTML Editor](https://github.com/Infomaniak/android-rich-html-editor) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Jetpack Compose](https://developer.android.com/jetpack/compose) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Kotlin Standard Library](https://kotlinlang.org/) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Kotlinx Coroutines](https://github.com/Kotlin/kotlinx.coroutines) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Lottie](https://github.com/airbnb/lottie-android) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Matomo SDK for Android](https://github.com/matomo-org/matomo-sdk-android) | [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause) |
+| [OkHttp](https://github.com/square/okhttp) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Okio](https://github.com/square/okio) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [QRose](https://github.com/alexzhirkevich/qrose) | [MIT License](https://opensource.org/licenses/MIT) |
+| [Realm Kotlin](https://github.com/realm/realm-kotlin) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [Sentry Android SDK](https://github.com/getsentry/sentry-java) | [MIT License](https://opensource.org/licenses/MIT) |
+| [Splitties](https://github.com/LouisCAD/Splitties) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
+| [ViewPager Dots Indicator](https://github.com/tommybuonomo/dotsindicator) | [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ce71dba089f..b078cc4f276 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -180,6 +180,7 @@ dependencies {
implementation(core.infomaniak.core.sentry)
implementation(core.infomaniak.core.twofactorauth.back.withuserdb)
implementation(core.infomaniak.core.twofactorauth.front)
+ implementation(core.infomaniak.core.ui.compose.contactcard)
implementation(core.infomaniak.core.ui)
implementation(core.infomaniak.core.ui.compose.basicbutton)
implementation(core.infomaniak.core.ui.compose.basics)
diff --git a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt
index 125e6139db3..f20c6051abc 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/AccountBottomSheetDialog.kt
@@ -22,7 +22,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
+import com.infomaniak.core.fragmentnavigation.safelyNavigate
import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.navArgs
import com.infomaniak.core.legacy.utils.context
import com.infomaniak.core.legacy.utils.safeBinding
import com.infomaniak.mail.MatomoMail.MatomoName
@@ -69,20 +71,28 @@ class AccountBottomSheetDialog : EdgeToEdgeBottomSheetDialog() {
private val switchUserViewModel: SwitchUserViewModel by activityViewModels()
- private val accountsAdapter = SwitchUserAdapter(
- currentUserId = AccountUtils.currentUserId,
- onChangingUserAccount = { user ->
- if (user.id == AccountUtils.currentUserId) {
- ConfettiUtils.onEasterEggConfettiClicked(
- container = (activity as? MainActivity)?.getConfettiContainer(),
- type = COLORED_SNOW,
- matomoValue = "Avatar",
- )
- } else {
- switchUserViewModel.switchAccount(user)
- }
- },
- )
+
+ private val navigationArgs: AccountBottomSheetDialogArgs by navArgs()
+ private val accountsAdapter by lazy {
+ SwitchUserAdapter(
+ currentUserId = AccountUtils.currentUserId,
+ onChangingUserAccount = { user ->
+ if (user.id == AccountUtils.currentUserId) {
+ ConfettiUtils.onEasterEggConfettiClicked(
+ container = (activity as? MainActivity)?.getConfettiContainer(),
+ type = COLORED_SNOW,
+ matomoValue = "Avatar",
+ )
+ } else {
+ switchUserViewModel.switchAccount(user)
+ }
+ },
+ onOpenContactCard = if (navigationArgs.showContactCard) { { user ->
+ if (user.id == AccountUtils.currentUserId) openContactCard(user.id)
+ } } else null,
+ )
+ }
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return BottomSheetAccountBinding.inflate(inflater, container, false).also { binding = it }.root
@@ -111,6 +121,11 @@ class AccountBottomSheetDialog : EdgeToEdgeBottomSheetDialog() {
showEasterEggHalloween()
}
+ override fun onResume() {
+ super.onResume()
+ accountsAdapter.animateCurrentUserQrCode(binding.recyclerViewAccount)
+ }
+
private fun logoutCurrentUser() = appScope.launch(ioDispatcher) {
trackAccountEvent(MatomoName.LogOutConfirm)
logoutUser(user = AccountUtils.currentUser!!)
@@ -136,4 +151,9 @@ class AccountBottomSheetDialog : EdgeToEdgeBottomSheetDialog() {
halloween.playAnimation()
}
}
+
+ private fun openContactCard(userId: Int) {
+ safelyNavigate(AccountBottomSheetDialogDirections.actionAccountBottomSheetDialogToContactCardFragment(userId))
+ dismiss()
+ }
}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/components/compose/MailTopAppbar.kt b/app/src/main/java/com/infomaniak/mail/ui/components/compose/MailTopAppbar.kt
index 32f0a04119e..7d38f01ed6f 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/components/compose/MailTopAppbar.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/components/compose/MailTopAppbar.kt
@@ -24,8 +24,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -36,16 +38,18 @@ import com.infomaniak.mail.ui.theme.MailTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MailTopAppBar(
+ title: @Composable () -> Unit = {},
navigationIcon: @Composable () -> Unit = {},
- actions: @Composable RowScope.() -> Unit = {}
+ actions: @Composable RowScope.() -> Unit = {},
+ containerColor: Color = MaterialTheme.colorScheme.background
) {
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
- containerColor = MaterialTheme.colorScheme.background,
+ containerColor = containerColor,
navigationIconContentColor = MaterialTheme.colorScheme.primary,
actionIconContentColor = MaterialTheme.colorScheme.primary
),
- title = {},
+ title = title,
navigationIcon = navigationIcon,
actions = actions
)
@@ -79,6 +83,15 @@ object TopAppBarButtons {
)
}
+@Composable
+fun MailTopAppBarTitle(text: String) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+}
+
@PreviewAllWindows
@Composable
private fun BrandTopAppBarPreview() {
diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/contactcard/ContactCardFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/contactcard/ContactCardFragment.kt
new file mode 100644
index 00000000000..a28680eece8
--- /dev/null
+++ b/app/src/main/java/com/infomaniak/mail/ui/main/contactcard/ContactCardFragment.kt
@@ -0,0 +1,209 @@
+/*
+ * Infomaniak Mail - 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 .
+ */
+package com.infomaniak.mail.ui.main.contactcard
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.infomaniak.core.auth.models.user.Card
+import com.infomaniak.core.ui.compose.contactcard.ContactCardDefaults
+import com.infomaniak.core.ui.compose.contactcard.ContactCardScreen
+import com.infomaniak.core.ui.compose.contactcard.ContactCardTopBarState
+import com.infomaniak.core.ui.compose.contactcard.R
+import com.infomaniak.core.ui.compose.contactcard.shareContactCard
+import com.infomaniak.mail.ui.alertDialogs.DescriptionAlertDialog
+import com.infomaniak.mail.ui.components.compose.MailTopAppBar
+import com.infomaniak.mail.ui.components.compose.MailTopAppBarTitle
+import com.infomaniak.mail.ui.components.compose.TopAppBarButton
+import com.infomaniak.mail.ui.components.compose.TopAppBarButtons
+import com.infomaniak.mail.ui.theme.LocalMailThemeColors
+import com.infomaniak.mail.ui.theme.MailTheme
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import com.infomaniak.core.common.R as RCore
+import com.infomaniak.mail.R as RMail
+
+@AndroidEntryPoint
+class ContactCardFragment : Fragment() {
+
+ @Inject
+ lateinit var descriptionDialog: DescriptionAlertDialog
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ descriptionDialog.bindAlertToLifecycle(viewLifecycleOwner)
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ return ComposeView(requireContext()).apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ MailTheme {
+ val mailColors = LocalMailThemeColors.current
+ ContactCardScreen(
+ onBack = { findNavController().popBackStack() },
+ onShare = ::shareCard,
+ confirmDelete = ::confirmDelete,
+ confirmValidationError = ::confirmValidationError,
+ topBar = { state -> MailContactCardTopBar(state) },
+ colors = ContactCardDefaults.colors(
+ waveBackground = mailColors.onboardingSecondaryBackground,
+ background = mailColors.backgroundHeaderColor,
+ ),
+ )
+ }
+ }
+ }
+ }
+
+ private fun shareCard(card: Card) {
+ lifecycleScope.launch {
+ requireActivity().shareContactCard(card)
+ }
+ }
+
+ private fun confirmDelete(onConfirmed: () -> Unit) {
+ descriptionDialog.show(
+ title = getString(R.string.deleteAlertTitle),
+ description = getString(R.string.deleteAlertDescription),
+ displayLoader = false,
+ positiveButtonText = RMail.string.actionDelete,
+ onPositiveButtonClicked = { onConfirmed() },
+ )
+ }
+
+ private fun confirmValidationError(onConfirmed: () -> Unit) {
+ descriptionDialog.show(
+ title = getString(R.string.alertTitle),
+ description = getString(R.string.alertDescription),
+ displayLoader = false,
+ positiveButtonText = android.R.string.ok,
+ onPositiveButtonClicked = { onConfirmed() },
+ )
+ }
+}
+
+@Composable
+private fun MailContactCardTopBar(state: ContactCardTopBarState) {
+ when (state) {
+ is ContactCardTopBarState.Editor -> MailContactCardTopBarEditor(state)
+ is ContactCardTopBarState.Preview -> MailContactCardTopBarPreview(state)
+ is ContactCardTopBarState.Default -> MailContactCardTopBarDefault(state)
+ }
+}
+
+@Composable
+private fun MailContactCardTopBarEditor(state: ContactCardTopBarState.Editor) {
+ MailTopAppBar(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ title = { MailTopAppBarTitle(stringResource(R.string.contactCardTitle)) },
+ navigationIcon = {
+ TextButton(onClick = state.onCancel) {
+ Text(
+ text = stringResource(RCore.string.buttonCancel),
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ },
+ actions = {
+ TextButton(onClick = state.onSave) {
+ Text(
+ text = stringResource(RCore.string.buttonSave),
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ },
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun MailContactCardTopBarEditorPreview() {
+ MailTheme {
+ MailContactCardTopBarEditor(
+ ContactCardTopBarState.Editor(
+ onCancel = {},
+ onSave = {},
+ ),
+ )
+ }
+}
+
+@Composable
+private fun MailContactCardTopBarPreview(state: ContactCardTopBarState.Preview) {
+ MailTopAppBar(
+ title = { MailTopAppBarTitle(stringResource(R.string.contactCardTitle)) },
+ navigationIcon = { TopAppBarButtons.Close(onClick = state.onClose) },
+ actions = {
+ TopAppBarButton(
+ icon = ImageVector.vectorResource(RMail.drawable.ic_param_dots),
+ contentDescResId = R.string.buttonMore,
+ onClick = state.onMore,
+ )
+ },
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun MailContactCardTopBarPreviewPreview() {
+ MailTheme {
+ MailContactCardTopBarPreview(
+ ContactCardTopBarState.Preview(
+ onClose = {},
+ onMore = {},
+ ),
+ )
+ }
+}
+
+@Composable
+private fun MailContactCardTopBarDefault(state: ContactCardTopBarState.Default) {
+ MailTopAppBar(
+ title = { MailTopAppBarTitle(stringResource(R.string.contactCardTitle)) },
+ navigationIcon = { TopAppBarButtons.Back(onClick = state.onBack) },
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun MailContactCardTopBarDefaultPreview() {
+ MailTheme {
+ MailContactCardTopBarDefault(
+ ContactCardTopBarState.Default(
+ onBack = {},
+ ),
+ )
+ }
+}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt
index e6df49ec6b5..54c7dc2204d 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListFragment.kt
@@ -131,6 +131,7 @@ import kotlinx.coroutines.launch
import java.util.Date
import javax.inject.Inject
import kotlin.math.abs
+import com.infomaniak.mail.ui.bottomSheetDialogs.AccountBottomSheetDialogArgs
import com.infomaniak.core.legacy.R as RCore
import com.infomaniak.core.legacy.utils.Utils as UtilsCore
@@ -522,7 +523,10 @@ class ThreadListFragment : TwoPaneFragment(), PickerEmojiObserver, MultiSelectio
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
- safeNavigate(resId = R.id.accountBottomSheetDialog)
+ safeNavigate(
+ resId = R.id.accountBottomSheetDialog,
+ args = AccountBottomSheetDialogArgs(showContactCard = true).toBundle(),
+ )
return true
}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/user/SwitchUserAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/user/SwitchUserAdapter.kt
index 71624eb5d32..a668b5bc95c 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/main/user/SwitchUserAdapter.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/main/user/SwitchUserAdapter.kt
@@ -17,20 +17,28 @@
*/
package com.infomaniak.mail.ui.main.user
+import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
+import android.view.animation.DecelerateInterpolator
import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import com.google.android.material.color.MaterialColors
import com.infomaniak.core.auth.models.user.User
+import com.infomaniak.mail.R
import com.infomaniak.mail.databinding.ItemSwitchUserAccountBinding
import com.infomaniak.mail.ui.main.user.SwitchUserAdapter.SwitchUserAccountViewHolder
+import com.google.android.material.R as RMaterial
@SuppressLint("NotifyDataSetChanged")
class SwitchUserAdapter(
val currentUserId: Int,
val onChangingUserAccount: ((User) -> Unit),
+ val onOpenContactCard: ((User) -> Unit)? = null,
) : Adapter() {
private var accounts: List = emptyList()
@@ -57,10 +65,21 @@ class SwitchUserAdapter(
userMailAddress.text = account.email
updateSelectedUi(position)
accountCardview.setOnClickListener { selectAccount(position) }
+ qrcode.setOnClickListener { if (account.id == currentUserId) onOpenContactCard?.invoke(account) }
}
private fun ItemSwitchUserAccountBinding.updateSelectedUi(position: Int) {
- checkmark.isVisible = accounts[position].id == currentUserId
+ val isCurrentUser = accounts[position].id == currentUserId
+ qrcode.isVisible = isCurrentUser && onOpenContactCard != null
+ qrcodeImage.background.alpha = (0.1 * 255).toInt()
+ accountCardview.isSelected = isCurrentUser
+ accountCardview.setCardBackgroundColor(
+ if (isCurrentUser && accounts.size > 1) {
+ MaterialColors.getColor(accountCardview, RMaterial.attr.colorPrimaryContainer)
+ } else {
+ accountCardview.context.getColor(R.color.backgroundColorSecondary)
+ }
+ )
}
private fun selectAccount(position: Int) = onChangingUserAccount(accounts[position])
@@ -72,5 +91,23 @@ class SwitchUserAdapter(
notifyDataSetChanged()
}
+ fun animateCurrentUserQrCode(recyclerView: RecyclerView) {
+ recyclerView.post {
+ for (i in 0 until recyclerView.childCount) {
+ val child = recyclerView.getChildAt(i)
+ val viewHolder = recyclerView.getChildViewHolder(child) as? SwitchUserAccountViewHolder ?: continue
+ val position = viewHolder.bindingAdapterPosition
+ if (position != RecyclerView.NO_POSITION && accounts.getOrNull(position)?.id == currentUserId) {
+ ObjectAnimator.ofFloat(viewHolder.binding.qrcode, View.ROTATION_Y, 180f, 360f).apply {
+ duration = 800
+ interpolator = DecelerateInterpolator()
+ start()
+ }
+ break
+ }
+ }
+ }
+ }
+
class SwitchUserAccountViewHolder(val binding: ItemSwitchUserAccountBinding) : ViewHolder(binding.root)
}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/noValidMailboxes/NoValidMailboxesFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/noValidMailboxes/NoValidMailboxesFragment.kt
index 5be152b32c8..61cb4eb2b6b 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/noValidMailboxes/NoValidMailboxesFragment.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/noValidMailboxes/NoValidMailboxesFragment.kt
@@ -30,6 +30,7 @@ import com.infomaniak.core.legacy.utils.safeNavigate
import com.infomaniak.mail.MatomoMail.MatomoName
import com.infomaniak.mail.MatomoMail.trackNoValidMailboxesEvent
import com.infomaniak.mail.R
+import com.infomaniak.mail.ui.bottomSheetDialogs.AccountBottomSheetDialogArgs
import com.infomaniak.mail.databinding.FragmentNoValidMailboxesBinding
import com.infomaniak.mail.ui.main.menuDrawer.InvalidMailboxesAdapter
import dagger.hilt.android.AndroidEntryPoint
@@ -67,7 +68,10 @@ class NoValidMailboxesFragment : Fragment() {
changeAccountButton.setOnClickListener {
trackNoValidMailboxesEvent(MatomoName.SwitchAccount)
- safeNavigate(resId = R.id.accountBottomSheetDialog)
+ safeNavigate(
+ resId = R.id.accountBottomSheetDialog,
+ args = AccountBottomSheetDialogArgs(showContactCard = false).toBundle(),
+ )
}
}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/theme/MailThemeColors.kt b/app/src/main/java/com/infomaniak/mail/ui/theme/MailThemeColors.kt
new file mode 100644
index 00000000000..7828b7b22f9
--- /dev/null
+++ b/app/src/main/java/com/infomaniak/mail/ui/theme/MailThemeColors.kt
@@ -0,0 +1,35 @@
+/*
+ * Infomaniak Mail - 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 .
+ */
+package com.infomaniak.mail.ui.theme
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+@Immutable
+data class MailThemeColors(
+ val onboardingSecondaryBackground: Color,
+ val backgroundHeaderColor: Color,
+)
+
+val LocalMailThemeColors = staticCompositionLocalOf {
+ MailThemeColors(
+ onboardingSecondaryBackground = Color.Unspecified,
+ backgroundHeaderColor = Color.Unspecified,
+ )
+}
diff --git a/app/src/main/java/com/infomaniak/mail/ui/theme/Theme.kt b/app/src/main/java/com/infomaniak/mail/ui/theme/Theme.kt
index 8543416a010..f07c59a458e 100644
--- a/app/src/main/java/com/infomaniak/mail/ui/theme/Theme.kt
+++ b/app/src/main/java/com/infomaniak/mail/ui/theme/Theme.kt
@@ -19,6 +19,7 @@ package com.infomaniak.mail.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import com.infomaniak.core.ui.compose.basics.Typography
@@ -32,14 +33,21 @@ fun MailTheme(content: @Composable () -> Unit) {
MaterialThemeFromXml {
val cornerSize = dimensionResource(R.dimen.bottomSheetCornerSize)
- ProvideBottomSheetTheme(
- theme = BottomSheetThemeDefaults.theme(
- shape = RoundedCornerShape(topStart = cornerSize, topEnd = cornerSize),
- dragHandleColor = colorResource(R.color.dragHandleColor),
- titleTextStyle = Typography.bodyMedium,
- titleColor = colorResource(R.color.primaryTextColor),
- ),
- content = content,
+ val mailThemeColors = MailThemeColors(
+ onboardingSecondaryBackground = colorResource(R.color.onboarding_secondary_background),
+ backgroundHeaderColor = colorResource(R.color.backgroundHeaderColor),
)
+
+ CompositionLocalProvider(LocalMailThemeColors provides mailThemeColors) {
+ ProvideBottomSheetTheme(
+ theme = BottomSheetThemeDefaults.theme(
+ shape = RoundedCornerShape(topStart = cornerSize, topEnd = cornerSize),
+ dragHandleColor = colorResource(R.color.dragHandleColor),
+ titleTextStyle = Typography.bodyMedium,
+ titleColor = colorResource(R.color.primaryTextColor),
+ ),
+ content = content,
+ )
+ }
}
}
diff --git a/app/src/main/java/com/infomaniak/mail/utils/AccountUtils.kt b/app/src/main/java/com/infomaniak/mail/utils/AccountUtils.kt
index 6d2b8a3d8a4..cfa189ca517 100644
--- a/app/src/main/java/com/infomaniak/mail/utils/AccountUtils.kt
+++ b/app/src/main/java/com/infomaniak/mail/utils/AccountUtils.kt
@@ -128,7 +128,8 @@ object AccountUtils : CredentialManager() {
if (remoteUser.id == currentUserId) {
remoteUser.organizations = arrayListOf()
requestCurrentUser()?.let { localUser ->
- setUserToken(remoteUser, localUser.apiToken)
+ val remoteUserWithLocalContactCard = remoteUser.copy(card = localUser.card)
+ setUserToken(remoteUserWithLocalContactCard, localUser.apiToken)
}
}
}
diff --git a/app/src/main/res/drawable/bg_reminder_icon.xml b/app/src/main/res/drawable/bg_reminder_icon.xml
new file mode 100644
index 00000000000..542122af17b
--- /dev/null
+++ b/app/src/main/res/drawable/bg_reminder_icon.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_pencil.xml b/app/src/main/res/drawable/ic_pencil_filled.xml
similarity index 100%
rename from app/src/main/res/drawable/ic_pencil.xml
rename to app/src/main/res/drawable/ic_pencil_filled.xml
diff --git a/app/src/main/res/drawable/ic_qrcode.xml b/app/src/main/res/drawable/ic_qrcode.xml
new file mode 100644
index 00000000000..18fb7593210
--- /dev/null
+++ b/app/src/main/res/drawable/ic_qrcode.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_thread_list.xml b/app/src/main/res/layout/fragment_thread_list.xml
index 3d0d45da0f5..b52bd353eb6 100644
--- a/app/src/main/res/layout/fragment_thread_list.xml
+++ b/app/src/main/res/layout/fragment_thread_list.xml
@@ -240,7 +240,7 @@
android:layout_marginEnd="@dimen/marginStandard"
android:layout_marginBottom="@dimen/marginStandard"
android:text="@string/buttonNewMessage"
- app:icon="@drawable/ic_pencil"
+ app:icon="@drawable/ic_pencil_filled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
diff --git a/app/src/main/res/layout/item_switch_user_account.xml b/app/src/main/res/layout/item_switch_user_account.xml
index 9bb5afd086f..1d2ca285dca 100644
--- a/app/src/main/res/layout/item_switch_user_account.xml
+++ b/app/src/main/res/layout/item_switch_user_account.xml
@@ -56,7 +56,7 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/marginStandardSmall"
app:layout_constraintBottom_toTopOf="@id/userMailAddress"
- app:layout_constraintEnd_toStartOf="@id/checkmark"
+ app:layout_constraintEnd_toStartOf="@id/qrcode"
app:layout_constraintStart_toEndOf="@id/userAvatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
@@ -68,23 +68,33 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/marginStandardSmall"
app:layout_constraintBottom_toTopOf="parent"
- app:layout_constraintEnd_toStartOf="@id/checkmark"
+ app:layout_constraintEnd_toStartOf="@id/qrcode"
app:layout_constraintStart_toEndOf="@id/userAvatar"
app:layout_constraintTop_toBottomOf="@id/userName"
tools:text="myriam.rais@infomaniak.com" />
-
+ app:layout_constraintTop_toTopOf="@id/userAvatar">
+
+
+
+
diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml
index e5c7446ea59..3de2520f18a 100644
--- a/app/src/main/res/navigation/main_navigation.xml
+++ b/app/src/main/res/navigation/main_navigation.xml
@@ -515,6 +515,15 @@
android:label="AccountManagementSettingsFragment"
tools:layout="@layout/fragment_account_management_settings" />
+
+
+
+
+ tools:layout="@layout/bottom_sheet_account">
+
+
+
+
+ tools:layout="@layout/bottom_sheet_account">
+
+
+
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 80866f2fc8a..179deeea6c9 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -217,6 +217,7 @@
Mappe valgt
Videresendt
Ny
+ Åbn kontaktkort
Svar
Lagringsstatus
Åbn mailhandlinger
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 688e1f723d3..9193b5b1cb1 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -217,6 +217,7 @@
Ordner ausgewählt
Weitergeleitet
Neu
+ Kontakt öffnen
Antwort
Speicherstatus
Mail-Aktionen öffnen
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index da11be5f7bc..670491754f5 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -217,6 +217,7 @@
Επιλεγμένος φάκελος
Προωθήθηκε
Νέο
+ Άνοιγμα κάρτας επαφής
Απάντηση
Κατάσταση αποθήκευσης
Άνοιγμα ενεργειών μηνύματος
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 70fe33ee587..4b3b3fd1cfa 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -217,6 +217,7 @@
Carpeta seleccionada
Reenviado
Nuevo
+ Abrir ficha de contacto
Respuesta
Estado de almacenamiento
Acciones de mensajes abiertos
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index d4f250e0912..b28bc18c042 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -217,6 +217,7 @@
Kansio valittu
Välitetty
Uusi
+ Avaa yhteystietokortti
Vastaus
Tallennustilan tila
Avaa sähköpostin toiminnot
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 53adfb40b21..7007368ea46 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -217,6 +217,7 @@
Dossier sélectionné
Transféré
Nouveau
+ Ouvrir la fiche contact
Réponse
État du stockage
Ouvrir les actions du message
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index f43e4e92b02..58fa30e03a6 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -217,6 +217,7 @@
Cartella selezionata
Inoltrato
Nuovo
+ Apri scheda contatto
Risposta
Stato di conservazione
Azioni di apertura dei messaggi
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index b49e0062b4c..cdd9ff56a3e 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -217,6 +217,7 @@
Mappe valgt
Videresendt
Ny
+ Åpne kontaktkort
Svar
Lagringsstatus
Åpne e-posthandlinger
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 728988635dd..a4c17a2bc0b 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -217,6 +217,7 @@
Map geselecteerd
Doorgestuurd
Nieuw
+ Open contactkaart
Antwoord
Opslagstatus
E-mailacties openen
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index d057aa80186..198491b7715 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -223,6 +223,7 @@
Folder wybrany
Przekazane dalej
Nowe
+ Otwórz kartę kontaktu
Odpowiedź
Status pamięci
Otwórz akcje wiadomości
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 43e93248f56..6f128b59b5f 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -217,6 +217,7 @@
Pasta selecionada
Encaminhado
Novo
+ Abrir ficha de contacto
Resposta
Estado do armazenamento
Abrir ações do e-mail
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 3beae10dcc2..37cb67ed77f 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -217,6 +217,7 @@
Mapp vald
Vidarebefordrad
Ny
+ Öppna kontaktkort
Svara
Lagringsstatus
Öppna e-poståtgärder
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 295f294ba9b..321b27044b7 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -221,6 +221,7 @@
Folder selected
Forwarded
New
+ Open contact card
Reply
Storage status
Open mail actions