First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2022-2025 New Vector 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.services.apperror.api"
}
dependencies {
implementation(libs.coroutines.core)
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.api
import androidx.compose.runtime.Immutable
@Immutable
sealed interface AppErrorState {
data object NoError : AppErrorState
data class Error(
val title: String,
val body: String,
val dismiss: () -> Unit,
) : AppErrorState
}
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.api
fun aAppErrorState() = AppErrorState.Error(
title = "An error occurred",
body = "Something went wrong, and the details of that would go here.",
dismiss = {},
)
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.api
import androidx.annotation.StringRes
import kotlinx.coroutines.flow.StateFlow
interface AppErrorStateService {
val appErrorStateFlow: StateFlow<AppErrorState>
fun showError(title: String, body: String)
fun showError(@StringRes titleRes: Int, @StringRes bodyRes: Int)
}
+36
View File
@@ -0,0 +1,36 @@
import extension.setupDependencyInjection
import extension.testCommonDependencies
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2022-2024 New Vector 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")
}
setupDependencyInjection()
android {
namespace = "io.element.android.services.apperror.impl"
}
dependencies {
implementation(projects.libraries.core)
implementation(projects.libraries.di)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.services.toolbox.api)
implementation(libs.coroutines.core)
implementation(libs.androidx.corektx)
api(projects.services.apperror.api)
testCommonDependencies(libs)
testImplementation(projects.services.toolbox.test)
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.impl
import androidx.compose.runtime.Composable
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.apperror.api.aAppErrorState
@Composable
fun AppErrorView(
state: AppErrorState,
) {
if (state is AppErrorState.Error) {
AppErrorViewContent(
title = state.title,
body = state.body,
onDismiss = state.dismiss,
)
}
}
@Composable
private fun AppErrorViewContent(
title: String,
body: String,
onDismiss: () -> Unit = { },
) {
ErrorDialog(
title = title,
content = body,
onSubmit = onDismiss,
)
}
@PreviewsDayNight
@Composable
internal fun AppErrorViewPreview() = ElementPreview {
AppErrorView(
state = aAppErrorState()
)
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.impl
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.apperror.api.AppErrorStateService
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class DefaultAppErrorStateService(
private val stringProvider: StringProvider,
) : AppErrorStateService {
private val currentAppErrorState = MutableStateFlow<AppErrorState>(AppErrorState.NoError)
override val appErrorStateFlow: StateFlow<AppErrorState> = currentAppErrorState
override fun showError(title: String, body: String) {
currentAppErrorState.value = AppErrorState.Error(
title = title,
body = body,
dismiss = {
currentAppErrorState.value = AppErrorState.NoError
},
)
}
override fun showError(titleRes: Int, bodyRes: Int) {
val title = stringProvider.getString(titleRes)
val body = stringProvider.getString(bodyRes)
showError(title, body)
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector 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.services.apperror.impl
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import kotlinx.coroutines.test.runTest
import org.junit.Test
internal class DefaultAppErrorStateServiceTest {
@Test
fun `initial value is no error`() = runTest {
val service = createDefaultAppErrorStateService()
service.appErrorStateFlow.test {
val state = awaitItem()
assertThat(state).isInstanceOf(AppErrorState.NoError::class.java)
}
}
@Test
fun `showError - emits value`() = runTest {
val service = createDefaultAppErrorStateService()
service.appErrorStateFlow.test {
skipItems(1)
service.showError("Title", "Body")
val state = awaitItem()
assertThat(state).isInstanceOf(AppErrorState.Error::class.java)
val errorState = state as AppErrorState.Error
assertThat(errorState.title).isEqualTo("Title")
assertThat(errorState.body).isEqualTo("Body")
}
}
@Test
fun `showError - emits value from ids`() = runTest {
val service = createDefaultAppErrorStateService()
service.appErrorStateFlow.test {
skipItems(1)
service.showError(1, 2)
val state = awaitItem()
assertThat(state).isInstanceOf(AppErrorState.Error::class.java)
val errorState = state as AppErrorState.Error
assertThat(errorState.title).isEqualTo("A string")
assertThat(errorState.body).isEqualTo("A string")
}
}
@Test
fun `dismiss - clears value`() = runTest {
val service = createDefaultAppErrorStateService()
service.appErrorStateFlow.test {
skipItems(1)
service.showError("Title", "Body")
val state = awaitItem()
assertThat(state).isInstanceOf(AppErrorState.Error::class.java)
val errorState = state as AppErrorState.Error
errorState.dismiss()
assertThat(awaitItem()).isInstanceOf(AppErrorState.NoError::class.java)
}
}
private fun createDefaultAppErrorStateService() = DefaultAppErrorStateService(
stringProvider = FakeStringProvider(),
)
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector 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-library")
}
android {
namespace = "io.element.android.services.apperror.test"
}
dependencies {
implementation(libs.coroutines.core)
implementation(projects.services.apperror.api)
implementation(projects.tests.testutils)
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector 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.services.apperror.test
import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.apperror.api.AppErrorStateService
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAppErrorStateService(
initialState: AppErrorState = AppErrorState.NoError,
private val showErrorResult: (String, String) -> Unit = { _, _ -> lambdaError() },
private val showErrorResResult: (Int, Int) -> Unit = { _, _ -> lambdaError() }
) : AppErrorStateService {
private val mutableAppErrorStateFlow = MutableStateFlow(initialState)
override val appErrorStateFlow: StateFlow<AppErrorState> = mutableAppErrorStateFlow.asStateFlow()
override fun showError(title: String, body: String) {
showErrorResult(title, body)
}
override fun showError(titleRes: Int, bodyRes: Int) {
showErrorResResult(titleRes, bodyRes)
}
fun setAppErrorState(state: AppErrorState) {
mutableAppErrorStateFlow.value = state
}
}