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