Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import androidx.datastore.preferences.preferencesDataStore
import com.wire.android.BuildConfig
import com.wire.android.feature.AppLockSource
import com.wire.android.ui.home.conversations.messages.item.MessageSwipeAction
import com.wire.android.ui.theme.ThemeOption
import com.wire.android.util.sha256
import com.wire.android.di.ApplicationContext
Expand Down Expand Up @@ -55,6 +56,8 @@
private val APP_LOCK_PASSCODE = stringPreferencesKey("app_lock_passcode")
private val APP_LOCK_SOURCE = intPreferencesKey("app_lock_source")
private val ENTER_TO_SENT = booleanPreferencesKey("enter_to_sent")
private val MESSAGE_SWIPE_RIGHT_ACTION = stringPreferencesKey("message_swipe_right_action")
private val MESSAGE_SWIPE_LEFT_ACTION = stringPreferencesKey("message_swipe_left_action")
private val ANONYMOUS_REGISTRATION_TRACK_ID = stringPreferencesKey("anonymous_registration_track_id")
private val IS_ANONYMOUS_REGISTRATION_ENABLED = booleanPreferencesKey("is_anonymous_registration_enabled")
private val PERSISTENT_WEBSOCKET_ENFORCED_BY_MDM = booleanPreferencesKey("persistent_websocket_enforced_by_mdm")
Expand Down Expand Up @@ -208,6 +211,22 @@
context.dataStore.edit { it[ENTER_TO_SENT] = enabled }
}

fun messageSwipeRightActionFlow(): Flow<MessageSwipeAction> =
getStringPreference(MESSAGE_SWIPE_RIGHT_ACTION, MessageSwipeAction.DEFAULT_RIGHT.name)
.map { MessageSwipeAction.fromStoredValue(it, MessageSwipeAction.DEFAULT_RIGHT) }

Check warning on line 216 in app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt#L215-L216

Added lines #L215 - L216 were not covered by tests

suspend fun setMessageSwipeRightAction(action: MessageSwipeAction) {
context.dataStore.edit { it[MESSAGE_SWIPE_RIGHT_ACTION] = action.name }

Check warning on line 219 in app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt#L219

Added line #L219 was not covered by tests
}

fun messageSwipeLeftActionFlow(): Flow<MessageSwipeAction> =
getStringPreference(MESSAGE_SWIPE_LEFT_ACTION, MessageSwipeAction.DEFAULT_LEFT.name)
.map { MessageSwipeAction.fromStoredValue(it, MessageSwipeAction.DEFAULT_LEFT) }

Check warning on line 224 in app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt#L223-L224

Added lines #L223 - L224 were not covered by tests

suspend fun setMessageSwipeLeftAction(action: MessageSwipeAction) {
context.dataStore.edit { it[MESSAGE_SWIPE_LEFT_ACTION] = action.name }

Check warning on line 227 in app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/datastore/GlobalDataStore.kt#L227

Added line #L227 was not covered by tests
}

fun isPersistentWebSocketEnforcedByMDM(): Flow<Boolean> =
getBooleanPreference(PERSISTENT_WEBSOCKET_ENFORCED_BY_MDM, false)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
deleteMessage = deleteMessage,
isWireCellFeatureEnabled = isWireCellsEnabled,
networkStateObserver = networkStateObserver,
globalDataStore = globalDataStore,

Check warning on line 223 in app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationCoreViewModelFactory.kt#L223

Added line #L223 was not covered by tests
)

fun messageComposerViewModel(savedStateHandle: SavedStateHandle) = MessageComposerViewModel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ import com.wire.android.ui.home.conversations.messages.draft.MessageDraftViewMod
import com.wire.android.ui.home.conversations.messages.item.AssetLocalPathArgs
import com.wire.android.ui.home.conversations.messages.item.MessageClickActions
import com.wire.android.ui.home.conversations.messages.item.MessageContainerItem
import com.wire.android.ui.home.conversations.messages.item.MessageSwipeAction
import com.wire.android.ui.home.conversations.messages.item.SwipeableMessageConfiguration
import com.wire.android.ui.home.conversations.messages.item.toSwipeAction
import com.wire.android.ui.home.conversations.migration.ConversationMigrationViewModel
import com.wire.android.ui.home.conversations.model.ExpirationStatus
import com.wire.android.ui.home.conversations.model.MessageSenderId
Expand Down Expand Up @@ -1076,6 +1078,8 @@ private fun ConversationScreen(
messageComposerStateHolder = messageComposerStateHolder,
attachments = attachments,
messages = conversationMessagesViewState.messages,
messageSwipeRightAction = conversationMessagesViewState.messageSwipeRightAction,
messageSwipeLeftAction = conversationMessagesViewState.messageSwipeLeftAction,
onSendMessage = onSendMessage,
onPingOptionClicked = onPingOptionClicked,
onImagesPicked = onImagesPicked,
Expand All @@ -1089,6 +1093,9 @@ private fun ConversationScreen(
onUpdateConversationReadDate = onUpdateConversationReadDate,
onShowEditingOptions = conversationScreenState::showEditContextMenu,
onSwipedToReply = messageComposerStateHolder::toReply,
onSwipedToDetails = { message ->
onMessageDetailsClick(message.header.messageId, message.isMyMessage)
},
onSelfDeletingMessageRead = onSelfDeletingMessageRead,
onFailedMessageCancelClicked = remember { { onDeleteMessage(it, false) } },
onFailedMessageRetryClicked = onFailedMessageRetryClicked,
Expand Down Expand Up @@ -1164,6 +1171,8 @@ private fun ConversationScreenContent(
messageComposerStateHolder: MessageComposerStateHolder,
attachments: List<AttachmentDraftUi>,
messages: Flow<PagingData<UIMessage>>,
messageSwipeRightAction: MessageSwipeAction,
messageSwipeLeftAction: MessageSwipeAction,
onSendMessage: (MessageBundle) -> Unit,
onPingOptionClicked: () -> Unit,
onImagesPicked: (List<Uri>, Boolean) -> Unit,
Expand All @@ -1177,6 +1186,7 @@ private fun ConversationScreenContent(
onUpdateConversationReadDate: (String) -> Unit,
onShowEditingOptions: (UIMessage.Regular) -> Unit,
onSwipedToReply: (UIMessage.Regular) -> Unit,
onSwipedToDetails: (UIMessage.Regular) -> Unit,
onSelfDeletingMessageRead: (UIMessage) -> Unit,
conversationDetailsData: ConversationDetailsData,
onFailedMessageRetryClicked: (String, ConversationId) -> Unit,
Expand Down Expand Up @@ -1238,6 +1248,9 @@ private fun ConversationScreenContent(
onSwipedToReact = { message ->
emojiPickerState.show(message.header.messageId)
},
onSwipedToDetails = onSwipedToDetails,
messageSwipeRightAction = messageSwipeRightAction,
messageSwipeLeftAction = messageSwipeLeftAction,
conversationDetailsData = conversationDetailsData,
selectedMessageId = selectedMessageId,
interactionAvailability = messageComposerStateHolder.messageComposerViewState.value.interactionAvailability,
Expand Down Expand Up @@ -1320,6 +1333,9 @@ fun MessageList(
onUpdateConversationReadDate: (String) -> Unit,
onSwipedToReply: (UIMessage.Regular) -> Unit,
onSwipedToReact: (UIMessage.Regular) -> Unit,
onSwipedToDetails: (UIMessage.Regular) -> Unit,
messageSwipeRightAction: MessageSwipeAction,
messageSwipeLeftAction: MessageSwipeAction,
onSelfDeletingMessageRead: (UIMessage) -> Unit,
conversationDetailsData: ConversationDetailsData,
selectedMessageId: String?,
Expand Down Expand Up @@ -1491,12 +1507,33 @@ fun MessageList(
}
}

val swipeableConfiguration = remember(message, lazyListState.isScrollInProgress) {
if (!lazyListState.isScrollInProgress && message is UIMessage.Regular && message.isSwipeable) {
SwipeableMessageConfiguration.Swipeable(
onSwipedRight = { onSwipedToReply(message) }.takeIf { message.isReplyable },
onSwipedLeft = { onSwipedToReact(message) }.takeIf { message.isReactionAllowed },
val swipeableConfiguration = remember(
message,
lazyListState.isScrollInProgress,
messageSwipeRightAction,
messageSwipeLeftAction,
) {
if (!lazyListState.isScrollInProgress && message is UIMessage.Regular) {
val rightSwipeAction = messageSwipeRightAction.toSwipeAction(
message = message,
onSwipedToReply = onSwipedToReply,
onSwipedToReact = onSwipedToReact,
onSwipedToDetails = onSwipedToDetails,
)
val leftSwipeAction = messageSwipeLeftAction.toSwipeAction(
message = message,
onSwipedToReply = onSwipedToReply,
onSwipedToReact = onSwipedToReact,
onSwipedToDetails = onSwipedToDetails,
)
if (rightSwipeAction != null || leftSwipeAction != null) {
SwipeableMessageConfiguration.Swipeable(
onSwipedRight = rightSwipeAction,
onSwipedLeft = leftSwipeAction,
)
} else {
SwipeableMessageConfiguration.NotSwipeable
}
} else {
SwipeableMessageConfiguration.NotSwipeable
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.paging.cachedIn
import com.ramcosta.composedestinations.generated.app.navArgs
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.media.audiomessage.ConversationAudioMessagePlayer
import com.wire.android.model.SnackBarMessage
import com.wire.android.ui.common.visbility.VisibilityState
Expand Down Expand Up @@ -113,6 +114,7 @@ class ConversationMessagesViewModel(
private val deleteMessage: DeleteMessageUseCase,
private val isWireCellFeatureEnabled: IsWireCellsEnabledUseCase,
private val networkStateObserver: NetworkStateObserver,
private val globalDataStore: GlobalDataStore,
) : ViewModel() {

private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs()
Expand Down Expand Up @@ -141,6 +143,7 @@ class ConversationMessagesViewModel(
observeAudioPlayerState()
observeAssetStatuses()
observeNetworkAvailability()
observeMessageSwipeAction()
}

private fun observeNetworkAvailability() {
Expand All @@ -153,6 +156,19 @@ class ConversationMessagesViewModel(
}
}

private fun observeMessageSwipeAction() {
viewModelScope.launch {
globalDataStore.messageSwipeRightActionFlow().collect { action ->
conversationViewState = conversationViewState.copy(messageSwipeRightAction = action)
}
}
viewModelScope.launch {
globalDataStore.messageSwipeLeftActionFlow().collect { action ->
conversationViewState = conversationViewState.copy(messageSwipeLeftAction = action)
}
}
}

val currentTimeInMillisFlow: Flow<Long> = flow {
while (true) {
delay(CURRENT_TIME_REFRESH_WINDOW_IN_MILLIS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.android.ui.home.conversations.messages

import androidx.paging.PagingData
import com.wire.android.media.audiomessage.PlayingAudioMessage
import com.wire.android.ui.home.conversations.messages.item.MessageSwipeAction
import com.wire.android.ui.home.conversations.model.AssetBundle
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.kalium.logic.data.message.MessageAssetStatus
Expand All @@ -40,6 +41,8 @@ data class ConversationMessagesViewState(
val isFetchingOlderMessages: Boolean = false,
val hasMoreRemoteMessages: Boolean = true,
val isNetworkAvailable: Boolean = false,
val messageSwipeRightAction: MessageSwipeAction = MessageSwipeAction.DEFAULT_RIGHT,
val messageSwipeLeftAction: MessageSwipeAction = MessageSwipeAction.DEFAULT_LEFT,
)

sealed class DownloadedAssetDialogVisibilityState {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* 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 http://www.gnu.org/licenses/.
*/

package com.wire.android.ui.home.conversations.messages.item

import com.wire.android.ui.home.conversations.model.UIMessage

enum class MessageSwipeAction {
REPLY,
REACT,
DETAILS;

companion object {
val DEFAULT_RIGHT = REPLY
val DEFAULT_LEFT = REACT

fun fromStoredValue(value: String?, defaultValue: MessageSwipeAction): MessageSwipeAction =
entries.firstOrNull { it.name == value } ?: defaultValue
}
}

internal fun MessageSwipeAction.toSwipeAction(
message: UIMessage.Regular,
onSwipedToReply: (UIMessage.Regular) -> Unit,
onSwipedToReact: (UIMessage.Regular) -> Unit,
onSwipedToDetails: (UIMessage.Regular) -> Unit,
): SwipeAction? =
when (this) {
MessageSwipeAction.REPLY -> SwipeAction(iconResId()) { onSwipedToReply(message) }
.takeIf { message.isReplyable }

MessageSwipeAction.REACT -> SwipeAction(iconResId()) { onSwipedToReact(message) }
.takeIf { message.isReactionAllowed }

MessageSwipeAction.DETAILS -> SwipeAction(iconResId()) { onSwipedToDetails(message) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@
import com.wire.android.ui.common.dimensions
import kotlin.math.absoluteValue
import kotlin.math.min
import com.wire.android.ui.common.R as commonR

@Stable
sealed interface SwipeableMessageConfiguration {
data object NotSwipeable : SwipeableMessageConfiguration
class Swipeable(
val onSwipedRight: (() -> Unit)? = null,
val onSwipedLeft: (() -> Unit)? = null,
val onSwipedRight: SwipeAction? = null,
val onSwipedLeft: SwipeAction? = null,

Check warning on line 55 in app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/SwipeableMessageBox.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/item/SwipeableMessageBox.kt#L54-L55

Added lines #L54 - L55 were not covered by tests
) : SwipeableMessageConfiguration
}

Expand All @@ -78,22 +79,19 @@
SwipeableBox(
messageStyle = messageStyle,
modifier = modifier,
onSwipeRight = swipeConfiguration?.onSwipedRight?.let {
SwipeAction(
icon = R.drawable.ic_reply,
action = it,
)
},
onSwipeLeft = swipeConfiguration?.onSwipedLeft?.let {
SwipeAction(
icon = R.drawable.ic_react,
action = it,
)
},
onSwipeRight = swipeConfiguration?.onSwipedRight,
onSwipeLeft = swipeConfiguration?.onSwipedLeft,
content = content,
)
}

fun MessageSwipeAction.iconResId(): Int =
when (this) {
MessageSwipeAction.REPLY -> R.drawable.ic_reply
MessageSwipeAction.REACT -> R.drawable.ic_react
MessageSwipeAction.DETAILS -> commonR.drawable.ic_info
}

@Suppress("CyclomaticComplexMethod")
@OptIn(ExperimentalFoundationApi::class)
@Composable
Expand Down
Loading
Loading