From c443306f5b066c7fa61727e38320469b869378cb Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 22 Jul 2026 15:21:37 +0200 Subject: [PATCH 1/6] refactor: Implement trailing content management for schedule options and bottom sheets --- .../SimpleSchedulePickerBottomSheet.kt | 10 ++++ .../mail/ui/main/settings/ItemSettingView.kt | 32 +++++++++++ .../ui/main/thread/actions/ActionItemView.kt | 33 +++--------- .../main/thread/actions/KSuiteChipManager.kt | 53 +++++++++++++++++++ .../actions/MailActionsBottomSheetDialog.kt | 2 - .../sendOptions/DraftSendOptionsFragment.kt | 24 ++++++++- 6 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt diff --git a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt index 97fd6264a58..6ab652a43b3 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt @@ -29,6 +29,7 @@ import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.core.legacy.utils.setBackNavigationResult import com.infomaniak.mail.databinding.BottomSheetScheduleOptionsBinding import com.infomaniak.mail.ui.main.thread.actions.ActionItemView +import com.infomaniak.mail.ui.main.thread.actions.TrailingContent import com.infomaniak.mail.utils.date.DateFormatUtils.dayOfWeekDateWithoutYear import com.infomaniak.mail.utils.openKSuiteProBottomSheet import com.infomaniak.mail.utils.openMailPremiumBottomSheet @@ -83,6 +84,14 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { (firstItem as? ActionItemView)?.setDividerVisibility(shouldDisplayDivider) } + protected open fun setupCustomScheduleOptionTrailing(kSuite: KSuite?) { + binding.customScheduleOption.trailingContent = when (kSuite) { + KSuite.Perso.Free -> TrailingContent.KSuitePersoChip + KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip + else -> TrailingContent.Chevron + } + } + protected fun setupScheduleOptions() = with(binding) { val lastDate = ScheduleOptionUtils.getLastScheduleOptionDate(lastSelectedEpoch, currentlyScheduledEpochMillis) if (lastDate != null) { @@ -103,6 +112,7 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { scheduleOptions.children.firstOrNull()?.let { firstItem -> setupFirstScheduleOptionDivider(firstItem, shouldDisplayDivider) } + setupCustomScheduleOptionTrailing(currentKSuite) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt index ff8ab5264f7..fb203f86c70 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt @@ -28,6 +28,8 @@ import com.google.android.material.materialswitch.MaterialSwitch import com.infomaniak.core.legacy.utils.getAttributes import com.infomaniak.mail.R import com.infomaniak.mail.databinding.ViewItemSettingBinding +import com.infomaniak.mail.ui.main.thread.actions.KSuiteChipManager +import com.infomaniak.mail.ui.main.thread.actions.TrailingContent class ItemSettingView @JvmOverloads constructor( context: Context, @@ -37,8 +39,16 @@ class ItemSettingView @JvmOverloads constructor( private val binding by lazy { ViewItemSettingBinding.inflate(LayoutInflater.from(context), this, true) } + private val kSuiteChipManager by lazy { KSuiteChipManager(binding.trailingChipContainer) } + private var action: Action = Action.NONE + var trailingContent = TrailingContent.None + set(value) { + field = value + setTrailingContentUi(value) + } + var isChecked get() = binding.toggle.isChecked set(value) { @@ -118,6 +128,28 @@ class ItemSettingView @JvmOverloads constructor( chevron.isGone = mustBlock } + fun setMyKSuiteChipVisibility(isVisible: Boolean) { + trailingContent = when { + isVisible -> TrailingContent.KSuitePersoChip + action == Action.CHEVRON -> TrailingContent.Chevron + else -> TrailingContent.None + } + } + + private fun setTrailingContentUi(trailingContent: TrailingContent) = with(binding) { + val hasChip = kSuiteChipManager.displayChipFor(trailingContent) + trailingChipContainer.isVisible = hasChip + + when (trailingContent) { + TrailingContent.KSuitePersoChip, TrailingContent.KSuiteProChip -> { + chevron.isGone = true + checkMark.isGone = true + } + TrailingContent.Chevron -> chevron.isVisible = true + else -> Unit + } + } + private enum class Action { NONE, CHEVRON, diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt index 9b1437a8e3c..7b97f4b886a 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt @@ -29,8 +29,6 @@ import androidx.annotation.StyleableRes import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isGone import androidx.core.view.isVisible -import com.infomaniak.core.ksuite.ksuitepro.views.EvolveChipView -import com.infomaniak.core.ksuite.myksuite.ui.views.MyKSuitePlusChipView import com.infomaniak.core.legacy.utils.getAttributes import com.infomaniak.core.ui.view.extension.setMarginsRelative import com.infomaniak.core.ui.view.extension.setPaddingRelative @@ -46,8 +44,7 @@ class ActionItemView @JvmOverloads constructor( private val binding by lazy { ItemBottomSheetActionBinding.inflate(LayoutInflater.from(context), this, true) } - private val kSuitePersoChipView by lazy { MyKSuitePlusChipView(context) } - private val kSuiteProChipView by lazy { EvolveChipView(context) } + private val kSuiteChipManager by lazy { KSuiteChipManager(binding.trailingContentLayout) } var trailingContent = TrailingContent.None set(value) { @@ -108,6 +105,10 @@ class ActionItemView @JvmOverloads constructor( fun setTitle(@StringRes textResourceId: Int) = binding.title.setText(textResourceId) + fun setTitle(text: String) { + binding.title.text = text + } + private fun setTitleColor(color: ColorStateList) = binding.title.setTextColor(color) fun setDescription(text: String) { @@ -121,8 +122,7 @@ class ActionItemView @JvmOverloads constructor( private fun setTrailingContentUi(trailingContent: TrailingContent) = with(binding) { trailingContentLayout.isVisible = true - trailingContentLayout.removeView(kSuitePersoChipView) - trailingContentLayout.removeView(kSuiteProChipView) + kSuiteChipManager.displayChipFor(trailingContent) when (trailingContent) { TrailingContent.None -> trailingContentLayout.isGone = true @@ -134,32 +134,13 @@ class ActionItemView @JvmOverloads constructor( description.isVisible = true actionIcon.isGone = true } - TrailingContent.KSuitePersoChip -> { - // ComposeView are not compatible with view without lifecycles (ex: PopupWindow in RecipientFieldView). - // This is causing a crash so to avoid that, we have to programmatically - // add the Compose view only where it's needed. - trailingContentLayout.addView(kSuitePersoChipView) - - actionIcon.isGone = true - description.isGone = true - } - TrailingContent.KSuiteProChip -> { - // ComposeView are not compatible with view without lifecycles (ex: PopupWindow in RecipientFieldView). - // This is causing a crash so to avoid that, we have to programmatically - // add the Compose view only where it's needed. - trailingContentLayout.addView(kSuiteProChipView) - + TrailingContent.KSuitePersoChip, TrailingContent.KSuiteProChip -> { actionIcon.isGone = true description.isGone = true } } } - /** Keep the entries order, it's used by the attribute (or change also the attributes order in attrs.xml) */ - enum class TrailingContent { - None, Chevron, Description, KSuitePersoChip, KSuiteProChip, - } - private fun TypedArray.getDimenOrNull(@StyleableRes index: Int): Int? { return getDimensionPixelSize(index, NOT_SET).takeIf { it != NOT_SET } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt new file mode 100644 index 00000000000..d3ae464bd16 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt @@ -0,0 +1,53 @@ +/* + * 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.thread.actions + +import android.view.ViewGroup +import com.infomaniak.core.ksuite.ksuitepro.views.EvolveChipView +import com.infomaniak.core.ksuite.myksuite.ui.views.MyKSuitePlusChipView + +class KSuiteChipManager(private val container: ViewGroup) { + + private val context get() = container.context + + private val kSuitePersoChipView by lazy { MyKSuitePlusChipView(context) } + private val kSuiteProChipView by lazy { EvolveChipView(context) } + + fun displayChipFor(trailingContent: TrailingContent): Boolean { + container.removeView(kSuitePersoChipView) + container.removeView(kSuiteProChipView) + + return when (trailingContent) { + // ComposeView are not compatible with view without lifecycles (ex: PopupWindow in RecipientFieldView). + // This is causing a crash so to avoid that, we have to programmatically + // add the Compose view only where it's needed. + TrailingContent.KSuitePersoChip -> container.addView(kSuitePersoChipView).let { true } + TrailingContent.KSuiteProChip -> container.addView(kSuiteProChipView).let { true } + else -> false + } + } +} + +/** Keep the entries order, it's used by the attribute (or change also the attributes order in attrs.xml) */ +enum class TrailingContent { + None, + Chevron, + Description, + KSuitePersoChip, + KSuiteProChip, +} diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt index ba2f1c92d3a..9318ba66e36 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/MailActionsBottomSheetDialog.kt @@ -32,7 +32,6 @@ import com.infomaniak.mail.data.models.extensions.kSuite import com.infomaniak.mail.databinding.BottomSheetActionsMenuBinding import com.infomaniak.mail.ui.MainViewModel import com.infomaniak.mail.ui.main.folder.TwoPaneViewModel -import com.infomaniak.mail.ui.main.thread.actions.ActionItemView.TrailingContent import com.infomaniak.mail.utils.AccountUtils import com.infomaniak.mail.utils.openMyKSuiteUpgradeBottomSheet import kotlinx.coroutines.runBlocking @@ -47,7 +46,6 @@ abstract class MailActionsBottomSheetDialog : ActionsBottomSheetDialog() { abstract val shouldCloseMultiSelection: Boolean protected abstract val substituteClassName: String - private var onClickListener: OnActionClick = object : OnActionClick { //region Main actions diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt index bf8da27ddd8..607c0d4bbef 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt @@ -41,6 +41,7 @@ import com.infomaniak.mail.ui.alertDialogs.SelectDateAndTimeForScheduledDraftDia import com.infomaniak.mail.ui.bottomSheetDialogs.ScheduleOption import com.infomaniak.mail.ui.bottomSheetDialogs.ScheduleOptionUtils import com.infomaniak.mail.ui.main.settings.SettingRadioButtonView +import com.infomaniak.mail.ui.main.thread.actions.TrailingContent import com.infomaniak.mail.ui.newMessage.NewMessageViewModel import com.infomaniak.mail.ui.newMessage.ReminderConfig import com.infomaniak.mail.ui.newMessage.ReminderPreset @@ -115,7 +116,9 @@ class DraftSendOptionsFragment : Fragment() { } } - private fun bindLastScheduleOptionDescription(description: String) = binding.lastScheduleOption.setDescription(description) + private fun setupCustomScheduleOptionTrailing(kSuite: KSuite?) { + binding.customScheduleOption.trailingContent = trailingContentFor(kSuite) + } private fun onLastScheduleOptionClicked() { newMessageViewModel.setScheduleConfig(lastSelectedEpoch?.let(ScheduleConfig::Scheduled) ?: ScheduleConfig.None) @@ -138,8 +141,8 @@ class DraftSendOptionsFragment : Fragment() { ScheduleOptionUtils.getAvailableScheduleOptions(currentlyScheduledEpochMillis).forEach { scheduleOption -> scheduleOptions.addView(createScheduleOptionItem(scheduleOption)) } - customScheduleOption.setOnClickListener { onCustomScheduleOptionClicked() } + setupCustomScheduleOptionTrailing(currentKSuite) } private fun setupToggles() = with(binding) { @@ -176,6 +179,8 @@ class DraftSendOptionsFragment : Fragment() { days3.setText(resources.getQuantityString(R.plurals.daysBeforeSendingReminder, 3, 3)) days7.setText(resources.getQuantityString(R.plurals.daysBeforeSendingReminder, 7, 7)) + customDelayReminder.trailingContent = trailingContentFor(currentKSuite) + val paddingStartValue = resources.getDimensionPixelSize(R.dimen.startPaddingWithoutIcon) (optionsDelays.children + customDelayReminder).forEach { view -> view.applyContentPaddingStart(paddingStartValue) } @@ -190,6 +195,14 @@ class DraftSendOptionsFragment : Fragment() { } ) } + + customDelayReminder.setOnClickListener { onCustomDelayReminderClicked() } + } + + private fun trailingContentFor(kSuite: KSuite?): TrailingContent = when (kSuite) { + KSuite.Perso.Free -> TrailingContent.KSuitePersoChip + KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip + else -> TrailingContent.Chevron } private fun setReminderOptionsVisible(isVisible: Boolean) { @@ -308,6 +321,8 @@ class DraftSendOptionsFragment : Fragment() { } } + private fun onCustomDelayReminderClicked() = executeIfAuthorized { showCustomDelayReminderDatePicker() } + private fun resetCustomDelayReminder() = with(binding) { customDelayReminder.setCheckMark(displayCheckMark = false) customDelayReminder.removeSubtitle() @@ -359,4 +374,9 @@ class DraftSendOptionsFragment : Fragment() { }, ) } + + private fun showCustomDelayReminderDatePicker() { + // TODO + } + } From 19e9b6d3ae7242c6854d12b745d6ebc7f690a886 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 11:36:26 +0200 Subject: [PATCH 2/6] fix: Correct matomo --- .../main/java/com/infomaniak/mail/MatomoMail.kt | 1 + .../sendOptions/DraftSendOptionsFragment.kt | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt index af3921770d9..82ae828b300 100644 --- a/app/src/main/java/com/infomaniak/mail/MatomoMail.kt +++ b/app/src/main/java/com/infomaniak/mail/MatomoMail.kt @@ -292,6 +292,7 @@ object MatomoMail : Matomo { SaveToKDrive("saveToKDrive"), ScheduleDraft("scheduleDraft"), ScheduledCustomDate("scheduledCustomDate"), + ReminderCustomDelta("reminderCustomDelta"), ScheduledDraftsFolder("scheduledDraftsFolder"), SeeAllAttendees("seeAllAttendees"), SelectAvatar("selectAvatar"), diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt index 607c0d4bbef..916782d2a67 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt @@ -124,7 +124,9 @@ class DraftSendOptionsFragment : Fragment() { newMessageViewModel.setScheduleConfig(lastSelectedEpoch?.let(ScheduleConfig::Scheduled) ?: ScheduleConfig.None) } - private fun onCustomScheduleOptionClicked() = executeIfAuthorized { showCustomScheduleDatePicker() } + private fun onCustomScheduleOptionClicked() { + executeIfAuthorized(MatomoName.ScheduledCustomDate.value) { showCustomScheduleDatePicker() } + } private fun setupScheduleOptions() = with(binding) { val lastDate = ScheduleOptionUtils.getLastScheduleOptionDate(lastSelectedEpoch, currentlyScheduledEpochMillis) @@ -321,7 +323,8 @@ class DraftSendOptionsFragment : Fragment() { } } - private fun onCustomDelayReminderClicked() = executeIfAuthorized { showCustomDelayReminderDatePicker() } + private fun onCustomDelayReminderClicked() = + executeIfAuthorized(MatomoName.ReminderCustomDelta.value) { showCustomDelayReminderDatePicker() } private fun resetCustomDelayReminder() = with(binding) { customDelayReminder.setCheckMark(displayCheckMark = false) @@ -353,11 +356,8 @@ class DraftSendOptionsFragment : Fragment() { } } - private fun executeIfAuthorized(onAuthorized: () -> Unit) { - val kSuite = currentKSuite - val matomoName = MatomoName.ScheduledCustomDate.value - - when (kSuite) { + private fun executeIfAuthorized(matomoName: String, onAuthorized: () -> Unit) { + when (val kSuite = currentKSuite) { KSuite.Perso.Free -> openMyKSuiteUpgradeBottomSheet(matomoName) KSuite.Pro.Free -> openKSuiteProBottomSheet(kSuite, navigationArgs.isAdmin, matomoName) KSuite.StarterPack -> openMailPremiumBottomSheet(matomoName) From 895a49ac1fe585d6063374996354e7c2f6b64cf7 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 29 Jul 2026 16:32:51 +0200 Subject: [PATCH 3/6] refactor: Remove unnecessary code --- .../SimpleSchedulePickerBottomSheet.kt | 12 ++++++------ .../sendOptions/DraftSendOptionsFragment.kt | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt index 6ab652a43b3..81ba3452810 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt @@ -67,7 +67,7 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { } } - protected open fun createScheduleOptionItem(scheduleOption: ScheduleOption): View { + private fun createScheduleOptionItem(scheduleOption: ScheduleOption): View { return ActionItemView(requireContext()).apply { setTitle(scheduleOption.titleRes) setDescription(context.dayOfWeekDateWithoutYear(date = scheduleOption.date())) @@ -76,16 +76,16 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { } } - protected open fun bindLastScheduleOptionDescription(description: String) { + private fun bindLastScheduleOptionDescription(description: String) { binding.lastScheduleOption.setDescription(description) } - protected open fun setupFirstScheduleOptionDivider(firstItem: View, shouldDisplayDivider: Boolean) { + private fun setupFirstScheduleOptionDivider(firstItem: View, shouldDisplayDivider: Boolean) { (firstItem as? ActionItemView)?.setDividerVisibility(shouldDisplayDivider) } - protected open fun setupCustomScheduleOptionTrailing(kSuite: KSuite?) { - binding.customScheduleOption.trailingContent = when (kSuite) { + private fun setupCustomScheduleOptionTrailing() { + binding.customScheduleOption.trailingContent = when (currentKSuite) { KSuite.Perso.Free -> TrailingContent.KSuitePersoChip KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip else -> TrailingContent.Chevron @@ -112,7 +112,7 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { scheduleOptions.children.firstOrNull()?.let { firstItem -> setupFirstScheduleOptionDivider(firstItem, shouldDisplayDivider) } - setupCustomScheduleOptionTrailing(currentKSuite) + setupCustomScheduleOptionTrailing() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt index 916782d2a67..e5dacacc305 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt @@ -116,8 +116,8 @@ class DraftSendOptionsFragment : Fragment() { } } - private fun setupCustomScheduleOptionTrailing(kSuite: KSuite?) { - binding.customScheduleOption.trailingContent = trailingContentFor(kSuite) + private fun setupCustomScheduleOptionTrailing() { + binding.customScheduleOption.trailingContent = trailingContentFor(currentKSuite) } private fun onLastScheduleOptionClicked() { @@ -143,8 +143,9 @@ class DraftSendOptionsFragment : Fragment() { ScheduleOptionUtils.getAvailableScheduleOptions(currentlyScheduledEpochMillis).forEach { scheduleOption -> scheduleOptions.addView(createScheduleOptionItem(scheduleOption)) } + customScheduleOption.setOnClickListener { onCustomScheduleOptionClicked() } - setupCustomScheduleOptionTrailing(currentKSuite) + setupCustomScheduleOptionTrailing() } private fun setupToggles() = with(binding) { From c623b653708fefeb4ae66553b89ca397ffb6771a Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 29 Jul 2026 16:49:52 +0200 Subject: [PATCH 4/6] refactor: Clean code --- .../mail/ui/main/settings/ItemSettingView.kt | 11 +------- .../main/thread/actions/KSuiteChipManager.kt | 19 ++++--------- .../ui/main/thread/actions/TrailingContent.kt | 27 +++++++++++++++++++ .../sendOptions/DraftSendOptionsFragment.kt | 3 ++- 4 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/TrailingContent.kt diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt index fb203f86c70..867c5bc604d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt @@ -128,17 +128,8 @@ class ItemSettingView @JvmOverloads constructor( chevron.isGone = mustBlock } - fun setMyKSuiteChipVisibility(isVisible: Boolean) { - trailingContent = when { - isVisible -> TrailingContent.KSuitePersoChip - action == Action.CHEVRON -> TrailingContent.Chevron - else -> TrailingContent.None - } - } - private fun setTrailingContentUi(trailingContent: TrailingContent) = with(binding) { - val hasChip = kSuiteChipManager.displayChipFor(trailingContent) - trailingChipContainer.isVisible = hasChip + trailingChipContainer.isVisible = kSuiteChipManager.displayChipFor(trailingContent) when (trailingContent) { TrailingContent.KSuitePersoChip, TrailingContent.KSuiteProChip -> { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt index d3ae464bd16..e6f918ed4c4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt @@ -31,23 +31,14 @@ class KSuiteChipManager(private val container: ViewGroup) { fun displayChipFor(trailingContent: TrailingContent): Boolean { container.removeView(kSuitePersoChipView) container.removeView(kSuiteProChipView) - - return when (trailingContent) { + val chipView = when (trailingContent) { // ComposeView are not compatible with view without lifecycles (ex: PopupWindow in RecipientFieldView). // This is causing a crash so to avoid that, we have to programmatically // add the Compose view only where it's needed. - TrailingContent.KSuitePersoChip -> container.addView(kSuitePersoChipView).let { true } - TrailingContent.KSuiteProChip -> container.addView(kSuiteProChipView).let { true } - else -> false + TrailingContent.KSuitePersoChip -> kSuitePersoChipView + TrailingContent.KSuiteProChip -> kSuiteProChipView + else -> null } + return chipView?.also(container::addView) != null } } - -/** Keep the entries order, it's used by the attribute (or change also the attributes order in attrs.xml) */ -enum class TrailingContent { - None, - Chevron, - Description, - KSuitePersoChip, - KSuiteProChip, -} diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/TrailingContent.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/TrailingContent.kt new file mode 100644 index 00000000000..1fe5e3a72c0 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/TrailingContent.kt @@ -0,0 +1,27 @@ +/* + * 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.thread.actions + +/** Keep the entries order, it's used by the attribute (or change also the attributes order in attrs.xml) */ +enum class TrailingContent { + None, + Chevron, + Description, + KSuitePersoChip, + KSuiteProChip, +} diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt index e5dacacc305..88be6040ebb 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt @@ -324,8 +324,9 @@ class DraftSendOptionsFragment : Fragment() { } } - private fun onCustomDelayReminderClicked() = + private fun onCustomDelayReminderClicked() { executeIfAuthorized(MatomoName.ReminderCustomDelta.value) { showCustomDelayReminderDatePicker() } + } private fun resetCustomDelayReminder() = with(binding) { customDelayReminder.setCheckMark(displayCheckMark = false) From 5a9fd028cbda8934bf8c1de19cb3254c7a611cb7 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 29 Jul 2026 17:10:36 +0200 Subject: [PATCH 5/6] refactor: Create extension for trailing content --- .../SimpleSchedulePickerBottomSheet.kt | 8 +--- .../sendOptions/DraftSendOptionsFragment.kt | 12 ++---- .../utils/extensions/TrailingContentExt.kt | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/utils/extensions/TrailingContentExt.kt diff --git a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt index 81ba3452810..dda91da6c21 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/bottomSheetDialogs/SimpleSchedulePickerBottomSheet.kt @@ -29,7 +29,7 @@ import com.infomaniak.core.legacy.utils.safeBinding import com.infomaniak.core.legacy.utils.setBackNavigationResult import com.infomaniak.mail.databinding.BottomSheetScheduleOptionsBinding import com.infomaniak.mail.ui.main.thread.actions.ActionItemView -import com.infomaniak.mail.ui.main.thread.actions.TrailingContent +import com.infomaniak.mail.utils.extensions.setKSuiteTrailingContent import com.infomaniak.mail.utils.date.DateFormatUtils.dayOfWeekDateWithoutYear import com.infomaniak.mail.utils.openKSuiteProBottomSheet import com.infomaniak.mail.utils.openMailPremiumBottomSheet @@ -85,11 +85,7 @@ abstract class SimpleSchedulePickerBottomSheet : EdgeToEdgeBottomSheetDialog() { } private fun setupCustomScheduleOptionTrailing() { - binding.customScheduleOption.trailingContent = when (currentKSuite) { - KSuite.Perso.Free -> TrailingContent.KSuitePersoChip - KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip - else -> TrailingContent.Chevron - } + binding.customScheduleOption.setKSuiteTrailingContent(currentKSuite) } protected fun setupScheduleOptions() = with(binding) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt index 88be6040ebb..cc97a1dbe53 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/sendOptions/DraftSendOptionsFragment.kt @@ -41,7 +41,6 @@ import com.infomaniak.mail.ui.alertDialogs.SelectDateAndTimeForScheduledDraftDia import com.infomaniak.mail.ui.bottomSheetDialogs.ScheduleOption import com.infomaniak.mail.ui.bottomSheetDialogs.ScheduleOptionUtils import com.infomaniak.mail.ui.main.settings.SettingRadioButtonView -import com.infomaniak.mail.ui.main.thread.actions.TrailingContent import com.infomaniak.mail.ui.newMessage.NewMessageViewModel import com.infomaniak.mail.ui.newMessage.ReminderConfig import com.infomaniak.mail.ui.newMessage.ReminderPreset @@ -49,6 +48,7 @@ import com.infomaniak.mail.ui.newMessage.ScheduleConfig import com.infomaniak.mail.utils.date.DateFormatUtils.dayOfWeekDateWithoutYear import com.infomaniak.mail.utils.date.DateFormatUtils.formatDelayText import com.infomaniak.mail.utils.extensions.applyContentPaddingStart +import com.infomaniak.mail.utils.extensions.setKSuiteTrailingContent import com.infomaniak.mail.utils.openKSuiteProBottomSheet import com.infomaniak.mail.utils.openMailPremiumBottomSheet import com.infomaniak.mail.utils.openMyKSuiteUpgradeBottomSheet @@ -117,7 +117,7 @@ class DraftSendOptionsFragment : Fragment() { } private fun setupCustomScheduleOptionTrailing() { - binding.customScheduleOption.trailingContent = trailingContentFor(currentKSuite) + binding.customScheduleOption.setKSuiteTrailingContent(currentKSuite) } private fun onLastScheduleOptionClicked() { @@ -182,7 +182,7 @@ class DraftSendOptionsFragment : Fragment() { days3.setText(resources.getQuantityString(R.plurals.daysBeforeSendingReminder, 3, 3)) days7.setText(resources.getQuantityString(R.plurals.daysBeforeSendingReminder, 7, 7)) - customDelayReminder.trailingContent = trailingContentFor(currentKSuite) + customDelayReminder.setKSuiteTrailingContent(currentKSuite) val paddingStartValue = resources.getDimensionPixelSize(R.dimen.startPaddingWithoutIcon) (optionsDelays.children + customDelayReminder).forEach { view -> view.applyContentPaddingStart(paddingStartValue) } @@ -202,12 +202,6 @@ class DraftSendOptionsFragment : Fragment() { customDelayReminder.setOnClickListener { onCustomDelayReminderClicked() } } - private fun trailingContentFor(kSuite: KSuite?): TrailingContent = when (kSuite) { - KSuite.Perso.Free -> TrailingContent.KSuitePersoChip - KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip - else -> TrailingContent.Chevron - } - private fun setReminderOptionsVisible(isVisible: Boolean) { TransitionManager.beginDelayedTransition(binding.reminderOptionsWrapper.parent as ViewGroup) binding.reminderOptionsWrapper.isVisible = isVisible diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/TrailingContentExt.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/TrailingContentExt.kt new file mode 100644 index 00000000000..60c6cf2652b --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/TrailingContentExt.kt @@ -0,0 +1,38 @@ +/* + * 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.utils.extensions + +import com.infomaniak.core.ksuite.data.KSuite +import com.infomaniak.mail.ui.main.settings.ItemSettingView +import com.infomaniak.mail.ui.main.thread.actions.ActionItemView +import com.infomaniak.mail.ui.main.thread.actions.TrailingContent + +fun KSuite?.toTrailingContent(default: TrailingContent = TrailingContent.Chevron): TrailingContent = when (this) { + KSuite.Perso.Free -> TrailingContent.KSuitePersoChip + KSuite.Pro.Free, KSuite.StarterPack -> TrailingContent.KSuiteProChip + else -> default +} + +fun ActionItemView.setKSuiteTrailingContent(kSuite: KSuite?, default: TrailingContent = TrailingContent.Chevron) { + trailingContent = kSuite.toTrailingContent(default) +} + +fun ItemSettingView.setKSuiteTrailingContent(kSuite: KSuite?, default: TrailingContent = TrailingContent.Chevron) { + trailingContent = kSuite.toTrailingContent(default) +} From 78269d431b87abab789e465d90f307959c982ed6 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 29 Jul 2026 17:24:29 +0200 Subject: [PATCH 6/6] fix: Prevent leak by using val for view --- .../mail/ui/main/settings/ItemSettingView.kt | 4 ++-- .../mail/ui/main/thread/actions/ActionItemView.kt | 4 ++-- .../ui/main/thread/actions/KSuiteChipManager.kt | 13 ++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt index 867c5bc604d..c77b856fde4 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/settings/ItemSettingView.kt @@ -39,7 +39,7 @@ class ItemSettingView @JvmOverloads constructor( private val binding by lazy { ViewItemSettingBinding.inflate(LayoutInflater.from(context), this, true) } - private val kSuiteChipManager by lazy { KSuiteChipManager(binding.trailingChipContainer) } + private val kSuiteChipManager by lazy { KSuiteChipManager(context) } private var action: Action = Action.NONE @@ -129,7 +129,7 @@ class ItemSettingView @JvmOverloads constructor( } private fun setTrailingContentUi(trailingContent: TrailingContent) = with(binding) { - trailingChipContainer.isVisible = kSuiteChipManager.displayChipFor(trailingContent) + trailingChipContainer.isVisible = kSuiteChipManager.displayChipFor(binding.trailingChipContainer, trailingContent) when (trailingContent) { TrailingContent.KSuitePersoChip, TrailingContent.KSuiteProChip -> { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt index 7b97f4b886a..527400cc32f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/ActionItemView.kt @@ -44,7 +44,7 @@ class ActionItemView @JvmOverloads constructor( private val binding by lazy { ItemBottomSheetActionBinding.inflate(LayoutInflater.from(context), this, true) } - private val kSuiteChipManager by lazy { KSuiteChipManager(binding.trailingContentLayout) } + private val kSuiteChipManager by lazy { KSuiteChipManager(context) } var trailingContent = TrailingContent.None set(value) { @@ -122,7 +122,7 @@ class ActionItemView @JvmOverloads constructor( private fun setTrailingContentUi(trailingContent: TrailingContent) = with(binding) { trailingContentLayout.isVisible = true - kSuiteChipManager.displayChipFor(trailingContent) + kSuiteChipManager.displayChipFor(binding.trailingContentLayout, trailingContent) when (trailingContent) { TrailingContent.None -> trailingContentLayout.isGone = true diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt index e6f918ed4c4..fb3ea7e4e0d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/KSuiteChipManager.kt @@ -17,18 +17,17 @@ */ package com.infomaniak.mail.ui.main.thread.actions +import android.content.Context import android.view.ViewGroup import com.infomaniak.core.ksuite.ksuitepro.views.EvolveChipView +import com.infomaniak.core.ksuite.myksuite.ui.views.BaseMyKSuiteChipView import com.infomaniak.core.ksuite.myksuite.ui.views.MyKSuitePlusChipView -class KSuiteChipManager(private val container: ViewGroup) { +class KSuiteChipManager(context: Context) { + private val kSuitePersoChipView: BaseMyKSuiteChipView = MyKSuitePlusChipView(context) + private val kSuiteProChipView: EvolveChipView = EvolveChipView(context) - private val context get() = container.context - - private val kSuitePersoChipView by lazy { MyKSuitePlusChipView(context) } - private val kSuiteProChipView by lazy { EvolveChipView(context) } - - fun displayChipFor(trailingContent: TrailingContent): Boolean { + fun displayChipFor(container: ViewGroup, trailingContent: TrailingContent): Boolean { container.removeView(kSuitePersoChipView) container.removeView(kSuiteProChipView) val chipView = when (trailingContent) {