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 b056ec0d8..09628cc65 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 @@ -180,7 +180,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : ).first() } - parentChatMessage!!.activeUser = message.activeUser + parentChatMessage.activeUser = message.activeUser parentChatMessage.imageUrl?.let { binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE binding.messageQuote.quotedMessageImage.load(it) { @@ -207,7 +207,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) binding.messageQuote.quotedChatMessageView.setOnClickListener { - val chatActivity = commonMessageInterface as ChatActivity chatActivity.jumpToQuotedMessage(parentChatMessage) } } catch (e: Exception) { 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 c7b55dd2d..6d5ff4299 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -191,7 +191,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 @@ -428,7 +427,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 } } @@ -923,7 +922,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 9a50d7c68..76bf9e7d9 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 @@ -26,7 +26,6 @@ import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew -import com.nextcloud.talk.utils.preferences.AppPreferences import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope @@ -45,8 +44,7 @@ class OfflineFirstChatRepository @Inject constructor( private val chatDao: ChatMessagesDao, private val chatBlocksDao: ChatBlocksDao, private val network: ChatNetworkDataSource, - private val datastore: AppPreferences, - private val monitor: NetworkMonitor, + private val networkMonitor: NetworkMonitor, private val userProvider: CurrentUserProviderNew ) : ChatMessageRepository { @@ -71,8 +69,7 @@ class OfflineFirstChatRepository @Inject constructor( > > = MutableSharedFlow() - override val updateMessageFlow: - Flow + override val updateMessageFlow: Flow get() = _updateMessageFlow private val _updateMessageFlow: @@ -85,8 +82,7 @@ class OfflineFirstChatRepository @Inject constructor( private val _lastCommonReadFlow: MutableSharedFlow = MutableSharedFlow() - override val lastReadMessageFlow: - Flow + override val lastReadMessageFlow: Flow get() = _lastReadMessageFlow private val _lastReadMessageFlow: @@ -278,7 +274,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 @@ -469,7 +465,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 } 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 f2d30a83b..33b785094 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 @@ -10,7 +10,6 @@ package com.nextcloud.talk.conversationlist.data.network import android.util.Log import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource -import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository import com.nextcloud.talk.data.database.dao.ConversationsDao import com.nextcloud.talk.data.database.mappers.asEntity @@ -40,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> @@ -59,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) @@ -106,8 +105,8 @@ class OfflineFirstConversationsRepository @Inject constructor( private suspend fun getRoomsFromServer(): List? { var conversationsFromSync: List? = null - if (!monitor.isOnline.first()) { - Log.d(OfflineFirstChatRepository.TAG, "Device is offline, can't load conversations from server") + 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/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index b33c9ffbb..49b619ad5 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -61,7 +61,6 @@ import com.nextcloud.talk.translate.repositories.TranslateRepositoryImpl import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew -import com.nextcloud.talk.utils.preferences.AppPreferences import dagger.Module import dagger.Provides import okhttp3.OkHttpClient @@ -180,7 +179,6 @@ class RepositoryModule { chatMessagesDao: ChatMessagesDao, chatBlocksDao: ChatBlocksDao, dataSource: ChatNetworkDataSource, - appPreferences: AppPreferences, networkMonitor: NetworkMonitor, userProvider: CurrentUserProviderNew ): ChatMessageRepository { @@ -188,7 +186,6 @@ class RepositoryModule { chatMessagesDao, chatBlocksDao, dataSource, - appPreferences, networkMonitor, userProvider ) 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 3da80bd45..3c2678a91 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,30 +164,30 @@ 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) && currentConversation!!.remoteServer.isNullOrEmpty() && - 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) }