diff --git a/Core b/Core index f405b946f3..c121d0e96b 160000 --- a/Core +++ b/Core @@ -1 +1 @@ -Subproject commit f405b946f30308e16dbbac79186c89e490d93f65 +Subproject commit c121d0e96b4d99ef07383498b6518eb9516fa1f9 diff --git a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt index 776ff226d0..e958c1ef49 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/MainViewModel.kt @@ -31,7 +31,6 @@ import androidx.lifecycle.viewModelScope import com.infomaniak.core.common.utils.DownloadManagerUtils import com.infomaniak.core.ksuite.data.KSuite import com.infomaniak.core.legacy.utils.SingleLiveEvent -import com.infomaniak.core.network.NetworkAvailability import com.infomaniak.core.network.models.ApiResponse import com.infomaniak.core.network.networking.HttpUtils import com.infomaniak.core.network.networking.ManualAuthorizationRequired @@ -76,6 +75,7 @@ import com.infomaniak.mail.utils.ContactUtils.getPhoneContacts import com.infomaniak.mail.utils.ContactUtils.mergeApiContactsIntoPhoneContacts import com.infomaniak.mail.utils.FeatureAvailability import com.infomaniak.mail.utils.MyKSuiteDataUtils +import com.infomaniak.mail.utils.NetworkManager import com.infomaniak.mail.utils.NotificationUtils.Companion.cancelNotification import com.infomaniak.mail.utils.SharedUtils import com.infomaniak.mail.utils.SharedUtils.Companion.updateSignatures @@ -142,6 +142,7 @@ class MainViewModel @Inject constructor( private val mergedContactController: MergedContactController, private val messageController: MessageController, private val myKSuiteDataUtils: MyKSuiteDataUtils, + private val networkManager: NetworkManager, private val permissionsController: PermissionsController, private val quotasController: QuotasController, private val refreshController: RefreshController, @@ -267,20 +268,10 @@ class MainViewModel @Inject constructor( val currentThreadsLive = MutableLiveData>() - val isNetworkAvailable = NetworkAvailability(appContext).isNetworkAvailable - var hasNetwork: Boolean = true - private set - private var currentThreadsLiveJob: Job? = null - init { - viewModelScope.launch { - isNetworkAvailable.collect { - SentryLog.d("Internet availability", if (it) "Available" else "Unavailable") - hasNetwork = it - } - } - } + val isNetworkAvailable = networkManager.isNetworkAvailable + val hasNetwork: Boolean get() = networkManager.hasNetwork fun reassignCurrentThreadsLive() { currentThreadsLiveJob?.cancel() 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 bbf42c777e..90b61e2cee 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 @@ -711,7 +711,6 @@ class ThreadListFragment : TwoPaneFragment(), PickerEmojiObserver { emoji = emoji, messageUid = messageUid, reactions = reactions, - hasNetwork = mainViewModel.hasNetwork, mailbox = mainViewModel.currentMailbox.value!! ) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt index 13146a8bb2..aed536b8ac 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt @@ -432,7 +432,6 @@ class ThreadFragment : Fragment(), PickerEmojiObserver { emoji = emoji, messageUid = messageUid, reactions = reactions, - hasNetwork = mainViewModel.hasNetwork, mailbox = mainViewModel.currentMailbox.value!!, onAllowed = { threadViewModel.fakeEmojiReply(emoji, messageUid) @@ -683,7 +682,6 @@ class ThreadFragment : Fragment(), PickerEmojiObserver { emoji = emoji, messageUid = messageUid, reactions = reactions, - hasNetwork = mainViewModel.hasNetwork, mailbox = mainViewModel.currentMailbox.value!!, onAllowed = { threadViewModel.fakeEmojiReply(emoji, messageUid) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/EmojiReactionsViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/EmojiReactionsViewModel.kt index 8495aa5671..bbabb012f5 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/EmojiReactionsViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/actions/EmojiReactionsViewModel.kt @@ -31,6 +31,7 @@ import com.infomaniak.mail.utils.AccountUtils import com.infomaniak.mail.utils.DraftInitManager import com.infomaniak.mail.utils.EmojiReactionUtils.hasAvailableReactionSlot import com.infomaniak.mail.utils.ErrorCode +import com.infomaniak.mail.utils.NetworkManager import com.infomaniak.mail.utils.Utils import com.infomaniak.mail.utils.extensions.appContext import com.infomaniak.mail.workers.DraftsActionsWorker @@ -46,6 +47,7 @@ class EmojiReactionsViewModel @Inject constructor( private val draftInitManager: DraftInitManager, private val draftsActionsWorkerScheduler: DraftsActionsWorker.Scheduler, private val messageController: MessageController, + private val networkManager: NetworkManager, private val snackbarManager: SnackbarManager, ) : AndroidViewModel(application) { /** @@ -55,16 +57,17 @@ class EmojiReactionsViewModel @Inject constructor( * If sending is allowed, the caller place can fake the emoji reaction locally thanks to [onAllowed]. * If sending is not allowed, it will display the error directly to the user and avoid doing the api call. */ + val hasNetwork: Boolean get() = networkManager.hasNetwork + fun trySendEmojiReply( emoji: String, messageUid: String, reactions: Map, - hasNetwork: Boolean, mailbox: Mailbox, onAllowed: () -> Unit = {}, ) { viewModelScope.launch { - when (val status = reactions.getEmojiSendStatus(emoji, hasNetwork)) { + when (val status = reactions.getEmojiSendStatus(emoji)) { EmojiSendStatus.Allowed -> { onAllowed() sendEmojiReply(emoji, messageUid, mailbox) @@ -74,7 +77,7 @@ class EmojiReactionsViewModel @Inject constructor( } } - private fun Map.getEmojiSendStatus(emoji: String, hasNetwork: Boolean): EmojiSendStatus = when { + private fun Map.getEmojiSendStatus(emoji: String): EmojiSendStatus = when { this[emoji]?.hasReacted == true -> EmojiSendStatus.NotAllowed.AlreadyUsed hasAvailableReactionSlot().not() -> EmojiSendStatus.NotAllowed.MaxReactionReached hasNetwork.not() -> EmojiSendStatus.NotAllowed.NoInternet diff --git a/app/src/main/java/com/infomaniak/mail/utils/NetworkManager.kt b/app/src/main/java/com/infomaniak/mail/utils/NetworkManager.kt new file mode 100644 index 0000000000..ff8e201f94 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/utils/NetworkManager.kt @@ -0,0 +1,52 @@ +/* + * 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 + +import com.infomaniak.core.network.NetworkAvailability +import com.infomaniak.core.sentry.SentryLog +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NetworkManager @Inject constructor() { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + /** + * A StateFlow that emits the current network availability status. + * It starts collecting immediately and keeps the latest value in memory. + */ + val isNetworkAvailable: StateFlow = NetworkAvailability().isNetworkAvailable + .onEach { available -> + SentryLog.d("NetworkManager", if (available) "Online" else "Offline") + } + .stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = true + ) + + val hasNetwork: Boolean + get() = isNetworkAvailable.value +}