Skip to content

Commit

Permalink
Merge branch 'develop' into feat/cherry-pick-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
yamilmedina authored Oct 13, 2023
2 parents 4f67d8d + 675404c commit 7217a4d
Show file tree
Hide file tree
Showing 117 changed files with 3,838 additions and 579 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package com.wire.kalium.plugins

import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget

fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean) {
fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean, enableIntegrationTests: Boolean = false) {
compilations.all {
kotlinOptions.jvmTarget = "17"
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
Expand All @@ -35,4 +35,9 @@ fun KotlinJvmTarget.commonJvmConfig(includeNativeInterop: Boolean) {
}
}
}
if (enableIntegrationTests) {
testRuns.getByName("integrationTest").executionTask.configure {
useJUnit()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class LibraryPlugin : Plugin<Project> {
val enableJs: Property<Boolean>
val enableJsTests: Property<Boolean>
val includeNativeInterop: Property<Boolean>
val enableIntegrationTests: Property<Boolean>
}

@get:Nested
Expand All @@ -53,7 +54,8 @@ class LibraryPlugin : Plugin<Project> {
enableApple = multiplatformConfiguration.enableApple.getOrElse(true),
enableJs = multiplatformConfiguration.enableJs.getOrElse(true),
enableJsTests = multiplatformConfiguration.enableJsTests.getOrElse(true),
includeNativeInterop = multiplatformConfiguration.includeNativeInterop.getOrElse(false)
includeNativeInterop = multiplatformConfiguration.includeNativeInterop.getOrElse(false),
enableIntegrationTests = multiplatformConfiguration.enableIntegrationTests.getOrElse(false)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fun Project.configureDefaultMultiplatform(
enableJs: Boolean,
enableJsTests: Boolean,
includeNativeInterop: Boolean,
enableIntegrationTests: Boolean,
androidNamespaceSuffix: String = this.name
) {
val kotlinExtension = extensions.findByName("kotlin") as? KotlinMultiplatformExtension
Expand All @@ -44,7 +45,7 @@ fun Project.configureDefaultMultiplatform(
}
kotlinExtension.apply {
targetHierarchy.default()
jvm { commonJvmConfig(includeNativeInterop) }
jvm { commonJvmConfig(includeNativeInterop, enableIntegrationTests) }

androidTarget { commmonKotlinAndroidTargetConfig() }

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ sqldelight = "2.0.0"
sqlcipher-android = "4.5.5"
pbandk = "0.14.2"
turbine = "1.0.0"
avs = "9.4.13"
avs = "9.4.14"
jna = "5.13.0"
core-crypto = "1.0.0-rc.12"
core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ abstract class CoreLogicCommon internal constructor(
proxyCredentials,
getGlobalScope().serverConfigRepository,
networkStateObserver,
kaliumConfigs::certPinningConfig
kaliumConfigs::certPinningConfig,
kaliumConfigs.kaliumMockEngine?.mockEngine
)

@Suppress("MemberVisibilityCanBePrivate") // Can be used by other targets like iOS and JS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ class GlobalKaliumScope internal constructor(
kaliumConfigs.developmentApiEnabled,
userAgent,
kaliumConfigs.ignoreSSLCertificatesForUnboundCalls,
kaliumConfigs.certPinningConfig
kaliumConfigs.certPinningConfig,
kaliumConfigs.kaliumMockEngine?.mockEngine
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class ClientMapper(
deviceType = deviceType?.let { fromDeviceTypeEntity(deviceType) },
label = label,
model = model,
isVerified = isVerified,
isVerified = isProteusVerified,
isValid = isValid,
mlsPublicKeys = mlsPublicKeys
)
Expand Down Expand Up @@ -192,7 +192,7 @@ class ClientMapper(

fun fromOtherUsersClientsDTO(otherUsersClients: List<ClientEntity>): List<OtherUserClient> =
otherUsersClients.map {
OtherUserClient(fromDeviceTypeEntity(it.deviceType), it.id, it.isValid, it.isVerified)
OtherUserClient(fromDeviceTypeEntity(it.deviceType), it.id, it.isValid, it.isProteusVerified)
}

private fun toDeviceTypeDTO(deviceType: DeviceType): DeviceTypeDTO = when (deviceType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ data class OtherUserClient(
val deviceType: DeviceType,
val id: String,
val isValid: Boolean,
val isVerified: Boolean
val isProteusVerified: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ interface ClientRepository {
suspend fun getClientsByUserId(userId: UserId): Either<StorageFailure, List<OtherUserClient>>
suspend fun observeClientsByUserId(userId: UserId): Flow<Either<StorageFailure, List<Client>>>

suspend fun updateClientVerificationStatus(
suspend fun updateClientProteusVerificationStatus(
userId: UserId,
clientId: ClientId,
verified: Boolean
Expand Down Expand Up @@ -213,12 +213,12 @@ class ClientDataSource(
.map { it.map { clientMapper.fromClientEntity(it) } }
.wrapStorageRequest()

override suspend fun updateClientVerificationStatus(
override suspend fun updateClientProteusVerificationStatus(
userId: UserId,
clientId: ClientId,
verified: Boolean
): Either<StorageFailure, Unit> = wrapStorageRequest {
clientDAO.updateClientVerificationStatus(userId.toDao(), clientId.value, verified)
clientDAO.updateClientProteusVerificationStatus(userId.toDao(), clientId.value, verified)
}

override suspend fun saveNewClientEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ internal class ConnectionMapperImpl(
qualifiedToId = qualifiedToId.toModel(),
status = statusMapper.fromDaoModel(status),
toId = toId,
fromUser = otherUser?.let { publicUserMapper.fromUserEntityToOtherUser(it) }
fromUser = otherUser?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(it) }
)
}

override fun fromDaoToConversationDetails(connection: ConnectionEntity): ConversationDetails = with(connection) {
ConversationDetails.Connection(
conversationId = qualifiedConversationId.toModel(),
otherUser = otherUser?.let { publicUserMapper.fromUserEntityToOtherUser(it) },
otherUser = otherUser?.let { publicUserMapper.fromUserDetailsEntityToOtherUser(it) },
userType = otherUser?.let { userTypeMapper.fromUserTypeEntity(it.userType) } ?: UserType.GUEST,
lastModifiedDate = lastUpdateDate.toIsoDateTimeString(),
connection = fromDaoToModel(this),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ internal class ConversationMapperImpl(
teamId = teamId?.let { TeamId(it) },
connectionStatus = connectionStatusMapper.fromDaoModel(connectionStatus),
expiresAt = null,
defederated = userDefederated ?: false
defederated = userDefederated ?: false,
isProteusVerified = false
),
legalHoldStatus = LegalHoldStatus.DISABLED,
userType = domainUserTypeMapper.fromUserTypeEntity(userType),
Expand Down Expand Up @@ -260,7 +261,8 @@ internal class ConversationMapperImpl(
previewPicture = previewAssetId?.toModel(),
teamId = teamId?.let { TeamId(it) },
expiresAt = null,
defederated = userDefederated ?: false
defederated = userDefederated ?: false,
isProteusVerified = false
)

ConversationDetails.Connection(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ interface ConversationRepository {
suspend fun getConversationDetailsByMLSGroupId(mlsGroupId: GroupID): Either<CoreFailure, ConversationDetails>

suspend fun observeUnreadArchivedConversationsCount(): Flow<Long>
suspend fun sendTypingIndicatorStatus(
conversationId: ConversationId,
typingStatus: Conversation.TypingIndicatorMode
): Either<CoreFailure, Unit>
}

@Suppress("LongParameterList", "TooManyFunctions")
Expand Down Expand Up @@ -869,6 +873,13 @@ internal class ConversationDataSource internal constructor(
.wrapStorageRequest()
.mapToRightOr(0L)

override suspend fun sendTypingIndicatorStatus(
conversationId: ConversationId,
typingStatus: Conversation.TypingIndicatorMode
): Either<CoreFailure, Unit> = wrapApiRequest {
conversationApi.sendTypingIndicatorNotification(conversationId.toApi(), typingStatus.toStatusDto())
}

private suspend fun persistIncompleteConversations(
conversationsFailed: List<NetworkQualifiedId>
): Either<CoreFailure, Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,25 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant

internal interface TypingIndicatorRepository {
internal interface TypingIndicatorIncomingRepository {
suspend fun addTypingUserInConversation(conversationId: ConversationId, userId: UserId)
suspend fun removeTypingUserInConversation(conversationId: ConversationId, userId: UserId)
suspend fun observeUsersTyping(conversationId: ConversationId): Flow<Set<ExpiringUserTyping>>
suspend fun observeUsersTyping(conversationId: ConversationId): Flow<Set<UserId>>
suspend fun clearExpiredTypingIndicators()
}

internal class TypingIndicatorRepositoryImpl(
private val userTypingCache: ConcurrentMutableMap<ConversationId, MutableSet<ExpiringUserTyping>>,
internal class TypingIndicatorIncomingRepositoryImpl(
private val userTypingCache: ConcurrentMutableMap<ConversationId, MutableSet<UserId>>,
private val userPropertyRepository: UserPropertyRepository
) : TypingIndicatorRepository {
) : TypingIndicatorIncomingRepository {

private val userTypingDataSourceFlow: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = BUFFER_SIZE, onBufferOverflow = BufferOverflow.DROP_OLDEST)

override suspend fun addTypingUserInConversation(conversationId: ConversationId, userId: UserId) {
if (userPropertyRepository.getTypingIndicatorStatus()) {
userTypingCache.safeComputeAndMutateSetValue(conversationId) { ExpiringUserTyping(userId, Clock.System.now()) }
userTypingCache.safeComputeAndMutateSetValue(conversationId) { userId }
.also {
userTypingDataSourceFlow.tryEmit(Unit)
}
Expand All @@ -55,36 +54,27 @@ internal class TypingIndicatorRepositoryImpl(

override suspend fun removeTypingUserInConversation(conversationId: ConversationId, userId: UserId) {
userTypingCache.block { entry ->
entry[conversationId]?.apply { this.removeAll { it.userId == userId } }
entry[conversationId]?.apply { this.removeAll { it == userId } }
}.also {
userTypingDataSourceFlow.tryEmit(Unit)
}
}

override suspend fun observeUsersTyping(conversationId: ConversationId): Flow<Set<ExpiringUserTyping>> {
override suspend fun observeUsersTyping(conversationId: ConversationId): Flow<Set<UserId>> {
return userTypingDataSourceFlow
.map { userTypingCache[conversationId] ?: emptySet() }
.onStart { emit(userTypingCache[conversationId] ?: emptySet()) }
}

companion object {
const val BUFFER_SIZE = 32 // drop after this threshold
}
}

// todo expire by worker
data class ExpiringUserTyping(
val userId: UserId,
val date: Instant
) {
override fun equals(other: Any?): Boolean {
return other != null && when (other) {
is ExpiringUserTyping -> other.userId == this.userId
else -> false
override suspend fun clearExpiredTypingIndicators() {
userTypingCache.block { entry ->
entry.clear()
}.also {
userTypingDataSourceFlow.tryEmit(Unit)
}
}

override fun hashCode(): Int {
return this.userId.hashCode()
companion object {
const val BUFFER_SIZE = 32 // drop after this threshold
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.kalium.logic.data.conversation

import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.properties.UserPropertyRepository
import com.wire.kalium.logic.functional.Either

internal interface TypingIndicatorOutgoingRepository {
suspend fun sendTypingIndicatorStatus(
conversationId: ConversationId,
typingStatus: Conversation.TypingIndicatorMode
): Either<CoreFailure, Unit>

}

internal class TypingIndicatorOutgoingRepositoryImpl(
private val typingIndicatorSenderHandler: TypingIndicatorSenderHandler,
private val userPropertyRepository: UserPropertyRepository
) : TypingIndicatorOutgoingRepository {

override suspend fun sendTypingIndicatorStatus(
conversationId: ConversationId,
typingStatus: Conversation.TypingIndicatorMode
): Either<CoreFailure, Unit> {
if (userPropertyRepository.getTypingIndicatorStatus()) {
when (typingStatus) {
Conversation.TypingIndicatorMode.STARTED ->
typingIndicatorSenderHandler.sendStartedAndEnqueueStoppingEvent(conversationId)

Conversation.TypingIndicatorMode.STOPPED -> typingIndicatorSenderHandler.sendStoppingEvent(conversationId)
}
}
return Either.Right(Unit)
}

}
Loading

0 comments on commit 7217a4d

Please sign in to comment.