Skip to content

Commit

Permalink
Merge branch 'develop' into feat/user_verification_status_proteus
Browse files Browse the repository at this point in the history
  • Loading branch information
borichellow committed Oct 4, 2023
2 parents 692cc81 + b2e2292 commit 6740a5d
Show file tree
Hide file tree
Showing 20 changed files with 665 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/jira-lint-and-link.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
# Run only if the PR is not from a Fork / external contributor
if: (!startsWith(github.ref, 'refs/heads/dependabot/') && github.repository_owner == 'wireapp')
steps:
- uses: cakeinpanic/jira-description-action@v0.6.1
- uses: cakeinpanic/jira-description-action@v0.7.0
name: jira-description-action
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetails
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.conversation.ObserveIsSelfUserMemberUseCase
import com.wire.kalium.logic.feature.conversation.ObserveUserListByIdUseCase
import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase
import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase
import com.wire.kalium.logic.feature.conversation.RemoveMemberFromConversationUseCase
import com.wire.kalium.logic.feature.conversation.RenameConversationUseCase
Expand Down Expand Up @@ -235,4 +236,9 @@ class ConversationModule {
fun provideObserveArchivedUnreadConversationsCountUseCase(
conversationScope: ConversationScope
): ObserveArchivedUnreadConversationsCountUseCase = conversationScope.observeArchivedUnreadConversationsCount

@ViewModelScoped
@Provides
fun provideObserveUsersTypingUseCase(conversationScope: ConversationScope): ObserveUsersTypingUseCase =
conversationScope.observeUsersTyping
}
19 changes: 17 additions & 2 deletions app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.wire.android.ui.home.conversations.avatar
import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant
import com.wire.android.ui.home.conversations.previewAsset
import com.wire.android.util.ui.WireSessionImageLoader
import com.wire.kalium.logic.data.message.UserSummary
import com.wire.kalium.logic.data.message.reaction.MessageReaction
import com.wire.kalium.logic.data.message.receipt.DetailedReceipt
import com.wire.kalium.logic.data.user.OtherUser
Expand Down Expand Up @@ -64,7 +65,7 @@ class UIParticipantMapper @Inject constructor(
id = userSummary.userId,
name = userSummary.userName.orEmpty(),
handle = userSummary.userHandle.orEmpty(),
avatarData = previewAsset(wireSessionImageLoader),
avatarData = userSummary.previewAsset(wireSessionImageLoader),
membership = userTypeMapper.toMembership(userSummary.userType),
unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(),
isDeleted = userSummary.isUserDeleted,
Expand All @@ -79,7 +80,7 @@ class UIParticipantMapper @Inject constructor(
id = userSummary.userId,
name = userSummary.userName.orEmpty(),
handle = userSummary.userHandle.orEmpty(),
avatarData = previewAsset(wireSessionImageLoader),
avatarData = userSummary.previewAsset(wireSessionImageLoader),
membership = userTypeMapper.toMembership(userSummary.userType),
unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(),
isDeleted = userSummary.isUserDeleted,
Expand All @@ -89,4 +90,18 @@ class UIParticipantMapper @Inject constructor(
isProteusVerified = false
)
}

fun toUIParticipant(userSummary: UserSummary): UIParticipant = with(userSummary) {
return UIParticipant(
id = userSummary.userId,
name = userSummary.userName.orEmpty(),
handle = userSummary.userHandle.orEmpty(),
avatarData = previewAsset(wireSessionImageLoader),
membership = userTypeMapper.toMembership(userSummary.userType),
unavailable = !userSummary.isUserDeleted && userSummary.userName.orEmpty().isEmpty(),
isDeleted = userSummary.isUserDeleted,
isSelf = false,
isDefederated = false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ fun UserProfileAvatar(
clickable: Clickable? = null,
showPlaceholderIfNoAsset: Boolean = true,
withCrossfadeAnimation: Boolean = false,
showStatusIndicator: Boolean = true
) {
Box(
contentAlignment = Alignment.Center,
Expand All @@ -82,10 +83,12 @@ fun UserProfileAvatar(
.testTag("User avatar"),
contentScale = ContentScale.Crop
)
UserStatusIndicator(
status = avatarData.availabilityStatus,
modifier = Modifier.align(Alignment.BottomEnd)
)
if (showStatusIndicator) {
UserStatusIndicator(
status = avatarData.availabilityStatus,
modifier = Modifier.align(Alignment.BottomEnd)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import com.wire.android.model.ImageAsset.UserAvatarAsset
import com.wire.android.model.UserAvatarData
import com.wire.android.util.ui.WireSessionImageLoader
import com.wire.kalium.logic.data.conversation.MemberDetails
import com.wire.kalium.logic.data.message.reaction.MessageReaction
import com.wire.kalium.logic.data.message.receipt.DetailedReceipt
import com.wire.kalium.logic.data.message.UserSummary
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.data.user.SelfUser
Expand Down Expand Up @@ -70,18 +69,10 @@ val MemberDetails.userType: UserType
is SelfUser -> UserType.INTERNAL
}

fun MessageReaction.previewAsset(
fun UserSummary.previewAsset(
wireSessionImageLoader: WireSessionImageLoader
) = UserAvatarData(
asset = this.userSummary.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) },
availabilityStatus = userSummary.availabilityStatus,
connectionState = userSummary.connectionStatus
)

fun DetailedReceipt.previewAsset(
wireSessionImageLoader: WireSessionImageLoader
) = UserAvatarData(
asset = this.userSummary.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) },
availabilityStatus = userSummary.availabilityStatus,
connectionState = userSummary.connectionStatus
asset = this.userPreviewAssetId?.let { UserAvatarAsset(wireSessionImageLoader, it) },
availabilityStatus = this.availabilityStatus,
connectionState = this.connectionStatus
)
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,7 @@ private fun ConversationScreenContent(
onClearMentionSearchResult = onClearMentionSearchResult,
onSendMessageBundle = onSendMessage,
tempWritableVideoUri = tempWritableVideoUri,
tempWritableImageUri = tempWritableImageUri

tempWritableImageUri = tempWritableImageUri,
)

// TODO: uncomment when we have the "scroll to bottom" button implemented
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/*
* Wire
* Copyright (C) 2023 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

import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.InfiniteTransition
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.sebaslogen.resaca.hilt.hiltViewModelScoped
import com.wire.android.R
import com.wire.android.model.UserAvatarData
import com.wire.android.ui.common.UserProfileAvatar
import com.wire.android.ui.common.colorsScheme
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant
import com.wire.android.ui.home.conversations.typing.TypingIndicatorViewModel
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID

const val MAX_PREVIEWS_DISPLAY = 3

@Composable
fun UsersTypingIndicatorForConversation(
conversationId: ConversationId,
viewModel: TypingIndicatorViewModel = hiltViewModelScoped(conversationId),
) {
UsersTypingIndicator(usersTyping = viewModel.usersTypingViewState.usersTyping)
}

@Composable
fun UsersTypingIndicator(usersTyping: List<UIParticipant>) {
if (usersTyping.isNotEmpty()) {
val rememberTransition =
rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(dimensions().spacing24x)
.background(
color = colorsScheme().surface,
shape = RoundedCornerShape(dimensions().corner14x),
)
) {
UsersTypingAvatarPreviews(usersTyping)
Text(
text = pluralStringResource(
R.plurals.typing_indicator_event_message,
usersTyping.size,
usersTyping.first().name,
usersTyping.size - 1
),
style = MaterialTheme.wireTypography.label01.copy(color = colorsScheme().secondaryText),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(weight = 1f, fill = false)
.padding(
top = dimensions().spacing4x,
bottom = dimensions().spacing4x,
end = dimensions().spacing8x,
)
)
HorizontalBouncingWritingPen(infiniteTransition = rememberTransition)
}
}
}

@Suppress("MagicNumber")
@Composable
private fun UsersTypingAvatarPreviews(usersTyping: List<UIParticipant>, maxPreviewsDisplay: Int = MAX_PREVIEWS_DISPLAY) {
usersTyping.take(maxPreviewsDisplay).forEachIndexed { index, user ->
val isSingleUser = usersTyping.size == 1 || maxPreviewsDisplay == 1
UserProfileAvatar(
avatarData = user.avatarData,
size = dimensions().spacing16x,
padding = dimensions().spacing2x,
showStatusIndicator = false,
modifier = if (isSingleUser) Modifier
else {
Modifier.offset(
x = if (index == 0) dimensions().spacing8x else -(dimensions().spacing6x)
)
}
)
}
}

@Suppress("MagicNumber")
@Composable
private fun HorizontalBouncingWritingPen(
infiniteTransition: InfiniteTransition,
) {
Row(modifier = Modifier.fillMaxHeight()) {
val position by infiniteTransition.animateFloat(
initialValue = -5f, targetValue = -1f,
animationSpec = infiniteRepeatable(
animation = tween(1_000, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = infiniteTransition.label
)

Icon(
imageVector = Icons.Default.MoreHoriz,
contentDescription = null,
tint = colorsScheme().secondaryText,
modifier = Modifier
.size(dimensions().spacing12x)
.offset(y = -dimensions().spacing2x)
.align(Alignment.Bottom)
)
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
tint = colorsScheme().secondaryText,
modifier = Modifier
.size(dimensions().spacing12x)
.offset(x = position.dp)
.align(Alignment.CenterVertically),
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewUsersTypingOne() {
Column(
modifier = Modifier
.background(color = colorsScheme().background)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
UsersTypingIndicator(
listOf(
UIParticipant(
id = QualifiedID("Alice", "wire.com"),
name = "Alice",
handle = "alice",
isSelf = false,
isService = false,
avatarData = UserAvatarData(),
membership = Membership.None,
connectionState = null,
unavailable = false,
isDeleted = false,
readReceiptDate = null,
botService = null,
isDefederated = false
)
)
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewUsersTypingMoreThanOne() {
Column(
modifier = Modifier
.background(color = colorsScheme().background)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
UsersTypingIndicator(
listOf(
UIParticipant(
id = QualifiedID("Bob", "wire.com"),
name = "Bob",
handle = "bob",
isSelf = false,
isService = false,
avatarData = UserAvatarData(),
membership = Membership.None,
connectionState = null,
unavailable = false,
isDeleted = false,
readReceiptDate = null,
botService = null,
isDefederated = false
),
UIParticipant(
id = QualifiedID("alice", "wire.com"),
name = "Alice Smith",
handle = "alice",
isSelf = false,
isService = false,
avatarData = UserAvatarData(),
membership = Membership.None,
connectionState = null,
unavailable = false,
isDeleted = false,
readReceiptDate = null,
botService = null,
isDefederated = false
)
)
)
}
}
Loading

0 comments on commit 6740a5d

Please sign in to comment.