diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 07ccb5cd3..dfb8b9e2e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -14,6 +14,7 @@ import android.util.Log import android.util.TypedValue import android.view.View import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.lifecycleScope import autodagger.AutoInjector import coil.load import com.google.android.flexbox.FlexboxLayout @@ -133,8 +134,10 @@ class OutcomingTextMessageViewHolder(itemView: View) : updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) } - CoroutineScope(Dispatchers.Main).launch { - if (message.isTemporary && !networkMonitor.isOnline.first()) { + val chatActivity = commonMessageInterface as ChatActivity + + chatActivity.lifecycleScope.launch { + if (message.isTemporary && !networkMonitor.isOnline.value) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, context.resources?.getString(R.string.nc_message_offline) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 225e3dc50..9af2aaa32 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -201,7 +201,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -452,7 +451,7 @@ class ChatActivity : this.lifecycleScope.launch { delay(DELAY_TO_SHOW_PROGRESS_BAR) - if (adapter?.isEmpty == true && networkMonitor.isOnline.first()) { + if (adapter?.isEmpty == true && networkMonitor.isOnline.value) { binding.progressBar.visibility = View.VISIBLE } } @@ -927,7 +926,7 @@ class ChatActivity : chatViewModel.getGeneralUIFlow.onEach { key -> when (key) { NO_OFFLINE_MESSAGES_FOUND -> { - if (networkMonitor.isOnline.first().not()) { + if (networkMonitor.isOnline.value.not()) { binding.offline.root.visibility = View.VISIBLE } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 08cf1b832..90ce591d8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -54,7 +54,7 @@ class OfflineFirstChatRepository @Inject constructor( private val chatDao: ChatMessagesDao, private val chatBlocksDao: ChatBlocksDao, private val network: ChatNetworkDataSource, - private val monitor: NetworkMonitor, + private val networkMonitor: NetworkMonitor, userProvider: CurrentUserProviderNew ) : ChatMessageRepository { @@ -303,7 +303,7 @@ class OfflineFirstChatRepository @Inject constructor( var showUnreadMessagesMarker = true while (true) { - if (!monitor.isOnline.first() || itIsPaused) { + if (!networkMonitor.isOnline.value || itIsPaused) { Thread.sleep(HALF_SECOND) } else { // sync database with server @@ -530,7 +530,7 @@ class OfflineFirstChatRepository @Inject constructor( } private suspend fun sync(bundle: Bundle): List? { - if (!monitor.isOnline.first()) { + if (!networkMonitor.isOnline.value) { Log.d(TAG, "Device is offline, can't load chat messages from server") return null } @@ -810,7 +810,7 @@ class OfflineFirstChatRepository @Inject constructor( sendWithoutNotification: Boolean, referenceId: String ): Flow> { - if (!monitor.isOnline.first()) { + if (!networkMonitor.isOnline.value) { return flow { emit(Result.failure(IOException("Skipped to send message as device is offline"))) } diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt index 3b68fc663..1485a0018 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -138,7 +138,6 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.apache.commons.lang3.builder.CompareToBuilder @@ -1359,7 +1358,7 @@ class ConversationsListActivity : override fun onItemLongClick(position: Int) { this.lifecycleScope.launch { - if (showShareToScreen || !networkMonitor.isOnline.first()) { + if (showShareToScreen || !networkMonitor.isOnline.value) { Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.") } else { val clickedItem: Any? = adapter!!.getItem(position) diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt index 816238a03..c4b941962 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt @@ -39,7 +39,7 @@ class OfflineFirstConversationsRepository @Inject constructor( private val dao: ConversationsDao, private val network: ConversationsNetworkDataSource, private val chatNetworkDataSource: ChatNetworkDataSource, - private val monitor: NetworkMonitor, + private val networkMonitor: NetworkMonitor, private val currentUserProviderNew: CurrentUserProviderNew ) : OfflineConversationsRepository { override val roomListFlow: Flow> @@ -58,7 +58,7 @@ class OfflineFirstConversationsRepository @Inject constructor( val initialConversationModels = getListOfConversations(user.id!!) _roomListFlow.emit(initialConversationModels) - if (monitor.isOnline.first()) { + if (networkMonitor.isOnline.value) { val conversationEntitiesFromSync = getRoomsFromServer() if (!conversationEntitiesFromSync.isNullOrEmpty()) { val conversationModelsFromSync = conversationEntitiesFromSync.map(ConversationEntity::asModel) @@ -108,7 +108,7 @@ class OfflineFirstConversationsRepository @Inject constructor( private suspend fun getRoomsFromServer(): List? { var conversationsFromSync: List? = null - if (!monitor.isOnline.first()) { + if (!networkMonitor.isOnline.value) { Log.d(TAG, "Device is offline, can't load conversations from server") return null } diff --git a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt index 2257c7f7c..f8d454849 100644 --- a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt +++ b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt @@ -2,13 +2,14 @@ * Nextcloud Talk - Android Client * * SPDX-FileCopyrightText: 2024 Julius Linus + * SPDX-FileCopyrightText: 2024 Marcel Hibbe * SPDX-License-Identifier: GPL-3.0-or-later */ package com.nextcloud.talk.data.network import androidx.lifecycle.LiveData -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * Utility for reporting app connectivity status. @@ -17,7 +18,7 @@ interface NetworkMonitor { /** * Returns the device's current connectivity status. */ - val isOnline: Flow + val isOnline: StateFlow /** * Returns the device's current connectivity status as LiveData for better interop with Java code. diff --git a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt index bc18240ce..b0763c724 100644 --- a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt @@ -2,6 +2,7 @@ * Nextcloud Talk - Android Client * * SPDX-FileCopyrightText: 2024 Julius Linus + * SPDX-FileCopyrightText: 2024 Marcel Hibbe * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -11,17 +12,17 @@ import android.content.Context import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities -import android.net.NetworkRequest.Builder +import android.util.Log import androidx.core.content.getSystemService import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn import javax.inject.Inject import javax.inject.Singleton @@ -29,49 +30,57 @@ import javax.inject.Singleton class NetworkMonitorImpl @Inject constructor( private val context: Context ) : NetworkMonitor { + + private val connectivityManager = context.getSystemService()!! + override val isOnlineLiveData: LiveData get() = isOnline.asLiveData() - override val isOnline: Flow = callbackFlow { - val connectivityManager = context.getSystemService() - if (connectivityManager == null) { - channel.trySend(false) - channel.close() - return@callbackFlow - } + override val isOnline: StateFlow get() = _isOnline - val networkRequest = Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() + private val _isOnline: StateFlow = callbackFlow { + val callback = object : ConnectivityManager.NetworkCallback() { + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + super.onCapabilitiesChanged(network, networkCapabilities) + val connected = networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_VALIDATED + ) + trySend(connected) + Log.d(TAG, "Network status changed: $connected") + } - val networkCallback = object : ConnectivityManager.NetworkCallback() { - private val networks = mutableSetOf() - - override fun onAvailable(network: Network) { - networks += network - channel.trySend(true) + override fun onUnavailable() { + super.onUnavailable() + trySend(false) + Log.d(TAG, "Network status: onUnavailable") } override fun onLost(network: Network) { - networks -= network - channel.trySend(networks.isNotEmpty()) + super.onLost(network) + trySend(false) + Log.d(TAG, "Network status: onLost") + } + + override fun onAvailable(network: Network) { + super.onAvailable(network) + trySend(true) + Log.d(TAG, "Network status: onAvailable") } } - connectivityManager.registerNetworkCallback(networkRequest, networkCallback) - - channel.trySend(connectivityManager.isCurrentlyConnected()) + connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { - connectivityManager.unregisterNetworkCallback(networkCallback) + connectivityManager.unregisterNetworkCallback(callback) } - } - .distinctUntilChanged() - .flowOn(Dispatchers.IO) - .conflate() + }.stateIn( + CoroutineScope(Dispatchers.IO), + SharingStarted.WhileSubscribed(COROUTINE_TIMEOUT), + false + ) - private fun ConnectivityManager.isCurrentlyConnected() = - activeNetwork - ?.let(::getNetworkCapabilities) - ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false + companion object { + private val TAG = NetworkMonitorImpl::class.java.simpleName + private const val COROUTINE_TIMEOUT = 5000L + } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 3ab24a2de..62614b47b 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -51,7 +51,6 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import java.util.Date import javax.inject.Inject @@ -134,7 +133,7 @@ class MessageActionsDialog( initMenuAddToNote( !message.isDeleted && !ConversationUtils.isNoteToSelfConversation(currentConversation) && - networkMonitor.isOnline.first(), + networkMonitor.isOnline.value, state.roomToken ) } @@ -147,16 +146,16 @@ class MessageActionsDialog( } } - initMenuItems() + initMenuItems(networkMonitor.isOnline.value) } - private fun initMenuItems() { + private fun initMenuItems(isOnline: Boolean) { this.lifecycleScope.launch { initMenuItemTranslate( !message.isDeleted && ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && CapabilitiesUtil.isTranslationsSupported(spreedCapabilities) && - networkMonitor.isOnline.first() + isOnline ) initMenuEditorDetails(message.lastEditTimestamp!! != 0L && !message.isDeleted) initMenuReplyToMessage(message.replyable && hasChatPermission) @@ -165,29 +164,29 @@ class MessageActionsDialog( hasUserId(user) && hasUserActorId(message) && currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL && - networkMonitor.isOnline.first() + isOnline ) initMenuEditMessage(isMessageEditable) - initMenuDeleteMessage(showMessageDeletionButton && networkMonitor.isOnline.first()) + initMenuDeleteMessage(showMessageDeletionButton && isOnline) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && !(message.isDeletedCommentMessage || message.isDeleted) && - networkMonitor.isOnline.first() + isOnline ) initMenuRemindMessage( !message.isDeleted && hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.REMIND_ME_LATER) && - networkMonitor.isOnline.first() + isOnline ) initMenuMarkAsUnread( message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && ChatMessage.MessageType.SYSTEM_MESSAGE != message.getCalculateMessageType() && - networkMonitor.isOnline.first() + isOnline ) - initMenuShare(messageHasFileAttachment || messageHasRegularText && networkMonitor.isOnline.first()) + initMenuShare(messageHasFileAttachment || messageHasRegularText && isOnline) initMenuItemOpenNcApp( ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == message.getCalculateMessageType() && - networkMonitor.isOnline.first() + isOnline ) initMenuItemSave(message.getCalculateMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index a75592532..482c068d5 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -23,7 +23,6 @@ import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -59,10 +58,9 @@ class TempMessageActionsDialog( private fun initMenuItems() { this.lifecycleScope.launch { - val isOnline = networkMonitor.isOnline.first() - initResendMessage(message.sendingFailed && isOnline) - initMenuEditMessage(message.sendingFailed || !isOnline) - initMenuDeleteMessage(message.sendingFailed || !isOnline) + initResendMessage(message.sendingFailed && networkMonitor.isOnline.value) + initMenuEditMessage(message.sendingFailed || !networkMonitor.isOnline.value) + initMenuDeleteMessage(message.sendingFailed || !networkMonitor.isOnline.value) initMenuItemCopy() } }