mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-05 12:54:35 +00:00
Add HealthChat API service layer (#1)
* feat(healthchat): add HealthChat API service layer New features/healthchat modules: typed Retrofit client with per-request Matrix token auth, member directory, profile and invite services, plus test fakes and fixtures. * test(healthchat): use runCatchingExceptions Detekt's RunCatchingNotAllowed rule forbids plain runCatching.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.healthchat.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.features.healthchat.api)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrix.test)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.healthchat.test
|
||||
|
||||
import io.element.android.features.healthchat.api.HealthChatDirectoryService
|
||||
import io.element.android.features.healthchat.api.HealthChatMember
|
||||
import io.element.android.features.healthchat.api.HealthChatMemberDirectory
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeHealthChatDirectoryService(
|
||||
private val searchMembersLambda: suspend (String) -> Result<HealthChatMemberDirectory> = { lambdaError() },
|
||||
private val getMemberLambda: suspend (UserId) -> Result<HealthChatMember> = { lambdaError() },
|
||||
) : HealthChatDirectoryService {
|
||||
override suspend fun searchMembers(query: String): Result<HealthChatMemberDirectory> {
|
||||
return searchMembersLambda(query)
|
||||
}
|
||||
|
||||
override suspend fun getMember(userId: UserId): Result<HealthChatMember> {
|
||||
return getMemberLambda(userId)
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.healthchat.test
|
||||
|
||||
import io.element.android.features.healthchat.api.HealthChatInviteService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeHealthChatInviteService(
|
||||
private val inviteLambda: suspend (String, String) -> Result<String> = { _, _ -> lambdaError() },
|
||||
) : HealthChatInviteService {
|
||||
override suspend fun invite(email: String, personalNote: String): Result<String> {
|
||||
return inviteLambda(email, personalNote)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.healthchat.test
|
||||
|
||||
import io.element.android.features.healthchat.api.HealthChatCompleteness
|
||||
import io.element.android.features.healthchat.api.HealthChatMember
|
||||
import io.element.android.features.healthchat.api.HealthChatProfileRepository
|
||||
import io.element.android.features.healthchat.api.HealthChatProfileUpdate
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class FakeHealthChatProfileRepository(
|
||||
private val refreshOwnProfileLambda: suspend () -> Result<HealthChatMember> = { lambdaError() },
|
||||
private val refreshCompletenessLambda: suspend () -> Result<HealthChatCompleteness> = { lambdaError() },
|
||||
private val updateOwnProfileLambda: suspend (HealthChatProfileUpdate) -> Result<String> = { lambdaError() },
|
||||
private val uploadAvatarLambda: suspend (String, String, ByteArray) -> Result<String> = { _, _, _ -> lambdaError() },
|
||||
private val deleteAvatarLambda: suspend () -> Result<String> = { lambdaError() },
|
||||
) : HealthChatProfileRepository {
|
||||
private val ownProfileFlow = MutableStateFlow<AsyncData<HealthChatMember>>(AsyncData.Uninitialized)
|
||||
override val ownProfile: StateFlow<AsyncData<HealthChatMember>> = ownProfileFlow
|
||||
|
||||
private val completenessFlow = MutableStateFlow<AsyncData<HealthChatCompleteness>>(AsyncData.Uninitialized)
|
||||
override val completeness: StateFlow<AsyncData<HealthChatCompleteness>> = completenessFlow
|
||||
|
||||
fun givenOwnProfile(value: AsyncData<HealthChatMember>) {
|
||||
ownProfileFlow.value = value
|
||||
}
|
||||
|
||||
fun givenCompleteness(value: AsyncData<HealthChatCompleteness>) {
|
||||
completenessFlow.value = value
|
||||
}
|
||||
|
||||
override suspend fun refreshOwnProfile(): Result<HealthChatMember> {
|
||||
return refreshOwnProfileLambda()
|
||||
.onSuccess { ownProfileFlow.value = AsyncData.Success(it) }
|
||||
.onFailure { ownProfileFlow.value = AsyncData.Failure(it) }
|
||||
}
|
||||
|
||||
override suspend fun refreshCompleteness(): Result<HealthChatCompleteness> {
|
||||
return refreshCompletenessLambda()
|
||||
.onSuccess { completenessFlow.value = AsyncData.Success(it) }
|
||||
.onFailure { completenessFlow.value = AsyncData.Failure(it) }
|
||||
}
|
||||
|
||||
override suspend fun updateOwnProfile(update: HealthChatProfileUpdate): Result<String> {
|
||||
return updateOwnProfileLambda(update)
|
||||
}
|
||||
|
||||
override suspend fun uploadAvatar(fileName: String, mimeType: String, data: ByteArray): Result<String> {
|
||||
return uploadAvatarLambda(fileName, mimeType, data)
|
||||
}
|
||||
|
||||
override suspend fun deleteAvatar(): Result<String> {
|
||||
return deleteAvatarLambda()
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Novaloop AG
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.healthchat.test
|
||||
|
||||
import io.element.android.features.healthchat.api.HealthChatCompleteness
|
||||
import io.element.android.features.healthchat.api.HealthChatMember
|
||||
import io.element.android.features.healthchat.api.HealthChatMemberDirectory
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
fun aHealthChatMember(
|
||||
userId: UserId = UserId("@erika.muster:healthchat.ch"),
|
||||
displayName: String? = "Dr. med. Erika Muster",
|
||||
avatarUrl: String? = null,
|
||||
title: String? = "Dr. med.",
|
||||
firstName: String? = "Erika",
|
||||
lastName: String? = "Muster",
|
||||
specialty: String? = "Allgemeinmedizin",
|
||||
practiceName: String? = "Praxis Muster",
|
||||
businessCity: String? = "Zürich",
|
||||
furtherInfo: String? = null,
|
||||
businessEmail: String? = "praxis@muster.ch",
|
||||
businessTel: String? = "+41 44 123 45 67",
|
||||
street: String? = "Musterstrasse",
|
||||
houseNumber: String? = "12",
|
||||
postalCode: String? = "8001",
|
||||
website: String? = "https://praxis-muster.ch",
|
||||
isApproved: Boolean = true,
|
||||
) = HealthChatMember(
|
||||
userId = userId,
|
||||
displayName = displayName,
|
||||
avatarUrl = avatarUrl,
|
||||
title = title,
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
specialty = specialty,
|
||||
practiceName = practiceName,
|
||||
businessCity = businessCity,
|
||||
furtherInfo = furtherInfo,
|
||||
businessEmail = businessEmail,
|
||||
businessTel = businessTel,
|
||||
street = street,
|
||||
houseNumber = houseNumber,
|
||||
postalCode = postalCode,
|
||||
website = website,
|
||||
isApproved = isApproved,
|
||||
)
|
||||
|
||||
fun aHealthChatMemberDirectory(
|
||||
members: List<HealthChatMember> = listOf(aHealthChatMember()),
|
||||
totalCount: Int = members.size,
|
||||
searchQuery: String = "",
|
||||
isApproved: Boolean = true,
|
||||
pendingMessage: String? = null,
|
||||
) = HealthChatMemberDirectory(
|
||||
members = members.toImmutableList(),
|
||||
totalCount = totalCount,
|
||||
searchQuery = searchQuery,
|
||||
isApproved = isApproved,
|
||||
pendingMessage = pendingMessage,
|
||||
)
|
||||
|
||||
fun aHealthChatCompleteness(
|
||||
isComplete: Boolean = true,
|
||||
missingFields: ImmutableList<String> = persistentListOf(),
|
||||
message: String? = null,
|
||||
hasProfilePhoto: Boolean = true,
|
||||
) = HealthChatCompleteness(
|
||||
isComplete = isComplete,
|
||||
missingFields = missingFields,
|
||||
message = message,
|
||||
hasProfilePhoto = hasProfilePhoto,
|
||||
)
|
||||
Reference in New Issue
Block a user