Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
01e2f4a
feat: Create reusable function to handle both disabled states with an…
Elouan1411 Apr 30, 2026
edd6304
feat: Add permission field
Elouan1411 Apr 30, 2026
d4a6c50
feat: Use new function to disabeld new message button
Elouan1411 Apr 30, 2026
3f1738d
feat: Use the canSendEmails parameter to dynamically change the button
Elouan1411 May 1, 2026
f35e606
feat: Block actions and disabled state color on BottomQuickActionBar
Elouan1411 May 1, 2026
05d4d7d
feat: Apply disabled state to send action in BottomQuickActionBar and…
Elouan1411 May 1, 2026
f4a82cd
feat: Disable writeEmail on DetailedContactBottomSheetDialog
Elouan1411 May 1, 2026
e354b5c
feat: Remove reply button in notification
Elouan1411 May 4, 2026
2b317d9
refactor: Add get function for canSendEmails
Elouan1411 May 4, 2026
5e34304
feat: Block send action in mailto links
Elouan1411 May 4, 2026
59fc05b
feat: Block the continue button in the SelectMailbox view
Elouan1411 May 4, 2026
1bf1af8
feat: Disabled dropDownMenuItem
Elouan1411 May 4, 2026
c0a9c2c
feat: Redirect to MainActivity when triggering mailto from outside kM…
Elouan1411 May 5, 2026
e742420
feat: Display snackbar on mailto link when only one mailbox is config…
Elouan1411 May 5, 2026
d8831c7
feat: Disabled reply button in ThreadAdapter
Elouan1411 May 5, 2026
70f7c12
refactor: Remove unused code and restore code accidentally deleted
Elouan1411 May 6, 2026
430a332
fix: Correct current mailbox selection
Elouan1411 May 6, 2026
82adc3d
fix: Set canSendEmails to true by default
Elouan1411 May 6, 2026
f20fa18
feat: Add onNewIntent support for Android versions below 15
Elouan1411 May 6, 2026
6b07acb
refactor: Reduce cognitive complexity
Elouan1411 May 6, 2026
eb7fc35
feat: Block reactions in item_message
Elouan1411 May 6, 2026
12a0099
refactor: Clean code
Elouan1411 May 6, 2026
470d320
fix: Reset extra when it is processed
Elouan1411 May 6, 2026
d4164c3
fix: Add observe for update UI dynamically
Elouan1411 May 6, 2026
0400674
feat: Use Flow to dynamically update the UI when permissions change
Elouan1411 May 6, 2026
479f2b1
refactor: Remove unused code
Elouan1411 May 7, 2026
fa49eaf
fix: Enable EmojiReactionView only if permission is true and canBeRea…
Elouan1411 May 7, 2026
4c09880
refactor: Clean code
Elouan1411 May 7, 2026
d9a7d8b
feat: Block button on schedule alert
Elouan1411 May 19, 2026
186b08c
refactor: Update flow construction and remove usage of canSendEmails
Elouan1411 May 22, 2026
12cb6c9
refactor: Change disableByMenuId and enableByMenuId to setEnableByMenuId
Elouan1411 May 22, 2026
5dcead5
refactor: Clean code
Elouan1411 May 22, 2026
391cfdf
refactor: Clean code
Elouan1411 May 26, 2026
51a7dfe
fix: Ensure correct Flow observation before launching activity
Elouan1411 May 26, 2026
64b5082
fix: Change setValue to postValue for coroutine compatibility
Elouan1411 May 26, 2026
baf4d50
fix: Use .value instead of .first because we use an eagerly collected…
Elouan1411 May 26, 2026
539db39
fix: Correct crash when opening an unread thread
Elouan1411 May 26, 2026
2bd5d3a
fix: Correct bug where second mailto link triggers no action
Elouan1411 May 27, 2026
363d986
fix: Disable onEmojiClick
Elouan1411 May 27, 2026
80fc3bb
fix: Remove singleTask
Elouan1411 May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ class EmojiReactionsView @JvmOverloads constructor(
) : FrameLayout(context, attrs, defStyleAttr) {

private val reactionsState = mutableStateListOf<Reaction>()
private var isAddReactionEnabled by mutableStateOf(true)
private var isViewEnabled by mutableStateOf(true)
private var isAddReactionEnabledForMessage by mutableStateOf(true)
private val isAddReactionEnabled: Boolean
get() = isViewEnabled && isAddReactionEnabledForMessage


private var addReactionClickListener: (() -> Unit)? = null
private var onEmojiClickListener: ((emoji: String) -> Unit)? = null
Expand All @@ -80,6 +84,11 @@ class EmojiReactionsView @JvmOverloads constructor(
addView(composeView)
}

override fun setEnabled(enabled: Boolean) {
super.setEnabled(enabled)
isViewEnabled = enabled
}
Comment thread
Elouan1411 marked this conversation as resolved.

private fun TypedArray.getDimensionOrNull(@StyleableRes index: Int): Float? {
return if (hasValue(index)) getDimension(index, -1f) else null
}
Expand All @@ -104,7 +113,7 @@ class EmojiReactionsView @JvmOverloads constructor(

EmojiReactions(
reactions = { reactionsState },
onEmojiClicked = { emoji -> onEmojiClickListener?.invoke(emoji) },
onEmojiClicked = { emoji -> if (isAddReactionEnabled) onEmojiClickListener?.invoke(emoji) },
shape = chipCornerRadius?.let { RoundedCornerShape(it) } ?: InputChipDefaults.shape,
addReactionIcon = addReactionIcon,
isAddReactionEnabled = { isAddReactionEnabled },
Expand Down Expand Up @@ -139,7 +148,7 @@ class EmojiReactionsView @JvmOverloads constructor(
}

fun setAddReactionEnabledState(isEnabled: Boolean) {
isAddReactionEnabled = isEnabled
isAddReactionEnabledForMessage = isEnabled
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ object RealmDatabase {

//region Configurations versions
const val USER_INFO_SCHEMA_VERSION = 5L
const val MAILBOX_INFO_SCHEMA_VERSION = 18L
const val MAILBOX_INFO_SCHEMA_VERSION = 19L
Comment thread
Elouan1411 marked this conversation as resolved.
const val MAILBOX_CONTENT_SCHEMA_VERSION = 36L
//endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class MailboxPermissions : EmbeddedRealmObject {
var canConfigureMailFolders: Boolean = false
@SerialName("can_restore_emails")
var canRestoreEmails: Boolean = false
@SerialName("can_send_emails")
var canSendEmails: Boolean = true

Comment thread
Elouan1411 marked this conversation as resolved.
companion object
}
25 changes: 22 additions & 3 deletions app/src/main/java/com/infomaniak/mail/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ class MainActivity : BaseActivity() {
syncDiscoveryManager.init(::showSyncDiscovery)

observeNotificationToRefresh()

handleAdminDisabledSendingSnackbarIfNeeded(intent)
}

private fun handleMenuDrawerEdgeToEdge() {
Expand Down Expand Up @@ -438,6 +440,18 @@ class MainActivity : BaseActivity() {
notificationManagerCompat.cancel(GENERIC_NEW_MAILS_NOTIFICATION_ID)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleAdminDisabledSendingSnackbarIfNeeded(intent)
}

private fun handleAdminDisabledSendingSnackbarIfNeeded(intent: Intent) {
if (!intent.getBooleanExtra(EXTRA_SHOW_ADMIN_DISABLED_SENDING_SNACKBAR, false)) return
snackbarManager.setValue(getString(R.string.snackbarAdminDisabledMessageSending))
Comment thread
Elouan1411 marked this conversation as resolved.
Comment thread
Elouan1411 marked this conversation as resolved.
intent.removeExtra(EXTRA_SHOW_ADMIN_DISABLED_SENDING_SNACKBAR)
setIntent(intent)
}

private fun handleOnBackPressed() = with(binding) {

fun closeDrawer() {
Expand Down Expand Up @@ -636,9 +650,13 @@ class MainActivity : BaseActivity() {
}

fun navigateToNewMessageActivity(args: Bundle? = null) {
val intent = Intent(this, NewMessageActivity::class.java)
args?.let(intent::putExtras)
newMessageActivityResultLauncher.launch(intent)
if (!mainViewModel.canSendEmailsFlow.value) {
snackbarManager.postValue(getString(R.string.snackbarAdminDisabledMessageSending))
} else {
val intent = Intent(this, NewMessageActivity::class.java)
args?.let(intent::putExtras)
newMessageActivityResultLauncher.launch(intent)
}
}

fun navigateToSyncAutoConfigActivity() {
Expand Down Expand Up @@ -675,6 +693,7 @@ class MainActivity : BaseActivity() {
const val SYNC_AUTO_CONFIG_KEY = "syncAutoConfigKey"
const val SYNC_AUTO_CONFIG_SUCCESS = "syncAutoConfigSuccess"
const val SYNC_AUTO_CONFIG_ALREADY_SYNC = "syncAutoConfigAlreadySync"
const val EXTRA_SHOW_ADMIN_DISABLED_SENDING_SNACKBAR = "show_admin_disabled_sending_snackbar"

private const val DEFAULT_APP_REVIEW_LAUNCHES = 50
private const val MAX_APP_REVIEW_LAUNCHES = 500
Expand Down
22 changes: 17 additions & 5 deletions app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,14 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
Expand Down Expand Up @@ -270,9 +272,19 @@ class MainViewModel @Inject constructor(
}
}

val currentPermissionsLive = _currentMailboxObjectId.flatMapLatest {
it?.let(permissionsController::getPermissionsAsync) ?: emptyFlow()
}.asLiveData(ioCoroutineContext)
private val _currentPermissionFlow = _currentMailboxObjectId.flatMapLatest {
it?.let(permissionsController::getPermissionsAsync) ?: flowOf(null)
}

val currentPermissionsLive = _currentPermissionFlow.filterNotNull().asLiveData(ioCoroutineContext)

val canSendEmailsFlow: StateFlow<Boolean> = _currentPermissionFlow.map { permissions ->
permissions?.canSendEmails ?: true
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = true
)
//endregion

//region Current Folder
Expand Down Expand Up @@ -484,8 +496,8 @@ class MainViewModel @Inject constructor(
SentryLog.d(TAG, "Force refresh Permissions")
with(ApiRepository.getPermissions(mailbox.accessId, mailbox.hostingId)) {
if (isSuccess()) {
mailboxController.updateMailbox(mailbox.objectId) {
it.permissions = data
mailboxController.updateMailbox(mailbox.objectId) { localMailbox ->
localMailbox.permissions = data
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import com.infomaniak.mail.utils.extensions.applySideAndBottomSystemInsets
import com.infomaniak.mail.utils.extensions.applyStatusBarInsets
import com.infomaniak.mail.utils.extensions.applyWindowInsetsListener
import com.infomaniak.mail.utils.extensions.bindAlertToViewLifecycle
import com.infomaniak.mail.utils.extensions.bindSendingClickListener
import com.infomaniak.mail.utils.extensions.observeNotNull
import com.infomaniak.mail.utils.extensions.safeArea
import com.infomaniak.mail.utils.extensions.safeNavigateToNewMessageActivity
Expand Down Expand Up @@ -469,10 +470,17 @@ class ThreadListFragment : TwoPaneFragment(), PickerEmojiObserver {
isHandled
}

newMessageFab.setOnClickListener {
trackNewMessageEvent(MatomoName.OpenFromFab)
safeNavigateToNewMessageActivity()
}
newMessageFab.bindSendingClickListener(
lifecycleOwner = viewLifecycleOwner,
canSendEmailsFlow = mainViewModel.canSendEmailsFlow,
onActionBlocked = {
snackbarManager.setValue(getString(R.string.snackbarAdminDisabledMessageSending))
},
onActionExecute = {
trackNewMessageEvent(MatomoName.OpenFromFab)
safeNavigateToNewMessageActivity()
}
)

threadsList.scrollListener = object : OnListScrollListener {
override fun onListScrollStateChanged(scrollState: ScrollState) = Unit
Expand Down Expand Up @@ -571,7 +579,7 @@ class ThreadListFragment : TwoPaneFragment(), PickerEmojiObserver {
private fun handleAccountSwipe(isSwipeDown: Boolean) = lifecycleScope.launch {
val accounts = switchUserViewModel.accounts.first()
if (accounts.isEmpty()) return@launch

val currentIndex = accounts.indexOfFirst { it.id == AccountUtils.currentUserId }
if (currentIndex == -1) return@launch

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs
import com.infomaniak.core.common.observe
import com.infomaniak.core.legacy.utils.safeBinding
import com.infomaniak.mail.MatomoMail.MatomoName
import com.infomaniak.mail.MatomoMail.trackContactActionsEvent
Expand Down Expand Up @@ -61,6 +62,8 @@ class DetailedContactBottomSheetDialog : ActionsBottomSheetDialog() {
contactDetails.setCorrespondent(navigationArgs.recipient, bimi)

setupListeners()

observeCanSendEmails()
}

private fun setupListeners() = with(binding) {
Expand All @@ -81,4 +84,10 @@ class DetailedContactBottomSheetDialog : ActionsBottomSheetDialog() {
copyRecipientEmailToClipboard(navigationArgs.recipient, snackbarManager)
}
}

private fun observeCanSendEmails() {
mainViewModel.canSendEmailsFlow.observe(viewLifecycleOwner) { canSend ->
binding.writeMail.isEnabled = canSend
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class MessageAlertView @JvmOverloads constructor(
}
}

override fun setEnabled(isEnabled: Boolean) = with(binding) {
super.setEnabled(isEnabled)
action1.isEnabled = isEnabled
action2.isEnabled = isEnabled
}

fun setDescription(text: String) {
binding.description.text = text
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class ThreadAdapter(
private val manuallyAllowedMessagesUids = mutableSetOf<String>()

private lateinit var recyclerView: RecyclerView

private var canSendEmails: Boolean = true
private val webViewUtils by lazy { WebViewUtils(recyclerView.context) }

private val scaledTouchSlop by lazy { ViewConfiguration.get(recyclerView.context).scaledTouchSlop }
Expand Down Expand Up @@ -194,10 +196,18 @@ class ThreadAdapter(
is NotifyType.MessagesCollapseStateChanged -> {
holder.handleMessagesCollapseStatePayload(item.message, isCollapsible = payload.isCollapsible)
}
NotifyType.UpdatePermissions -> holder.handlePermissionsPayload(canSendEmails)
}
}
}.getOrDefault(Unit)

fun updateEmailsPermission(canSend: Boolean) {
if (canSendEmails != canSend) {
canSendEmails = canSend
notifyItemRangeChanged(0, itemCount, NotifyType.UpdatePermissions)
}
}

private fun MessageViewHolder.handleToggleLightModePayload(messageUid: String) = with(threadAdapterState) {
isThemeTheSameMap[messageUid] = !isThemeTheSameMap[messageUid]!!
toggleContentAndQuoteTheme(messageUid)
Expand Down Expand Up @@ -246,7 +256,7 @@ class ThreadAdapter(
}

if (item is MessageUi) {
(holder as MessageViewHolder).bindMail(item, position)
(holder as MessageViewHolder).bindMail(item, position, canSendEmails)
} else {
(holder as SuperCollapsedBlockViewHolder).bindSuperCollapsedBlock(item as SuperCollapsedBlock)
}
Expand All @@ -262,7 +272,7 @@ class ThreadAdapter(
}
}

private fun MessageViewHolder.bindMail(messageUi: MessageUi, position: Int) {
private fun MessageViewHolder.bindMail(messageUi: MessageUi, position: Int, canSendEmails: Boolean) {

initMapForNewMessage(messageUi.message, position)

Expand All @@ -274,6 +284,13 @@ class ThreadAdapter(
bindEmojiReactions(messageUi)

onExpandOrCollapseMessage(messageUi.message, shouldTrack = false)
handlePermissionsPayload(canSendEmails)
}

private fun MessageViewHolder.handlePermissionsPayload(canSendEmails: Boolean) = with(binding) {
replyButton.isEnabled = canSendEmails
emojiReactions.isEnabled = canSendEmails
scheduleAlert.isEnabled = canSendEmails
}

private fun MessageViewHolder.bindCalendarEvent(message: Message) {
Expand Down Expand Up @@ -960,6 +977,7 @@ class ThreadAdapter(
data object OnlyRebindCalendarAttendance : NotifyType
data object OnlyRebindEmojiReactions : NotifyType
data object UnsubscribeRebind : NotifyType
data object UpdatePermissions : NotifyType
@JvmInline
value class MessagesCollapseStateChanged(val isCollapsible: Boolean) : NotifyType
}
Expand Down
Loading
Loading