diff --git a/core/ui/src/main/kotlin/dev/atick/core/ui/utils/CrashReporter.kt b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/CrashReporter.kt new file mode 100644 index 000000000..a117ccb5e --- /dev/null +++ b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/CrashReporter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2024 Atick Faisal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.atick.core.ui.utils + +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase + +/** + * Interface for reporting exceptions. + */ +interface CrashReporter { + /** + * Reports an exception. + * + * @param throwable The exception to be reported. + */ + fun reportException(throwable: Throwable) +} + +/** + * Implementation of [CrashReporter] that uses Firebase Crashlytics. + */ +class FirebaseCrashReporter() : CrashReporter { + /** + * Reports an exception to Firebase Crashlytics. + * + * @param throwable The exception to be reported. + */ + override fun reportException(throwable: Throwable) { + Firebase.crashlytics.recordException(throwable) + } +} diff --git a/core/ui/src/main/kotlin/dev/atick/core/ui/utils/StatefulComposable.kt b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/StatefulComposable.kt index 2cade84af..bd8c64b85 100644 --- a/core/ui/src/main/kotlin/dev/atick/core/ui/utils/StatefulComposable.kt +++ b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/StatefulComposable.kt @@ -22,8 +22,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase import dev.atick.core.ui.components.JetpackOverlayLoadingWheel import dev.atick.core.utils.OneTimeEvent import kotlinx.coroutines.CoroutineScope @@ -31,10 +29,20 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +/** + * A composable function that represents a stateful UI component. + * + * @param T The type of the data. + * @param state The current state of the UI. + * @param onShowSnackbar A suspend function to show a snackbar with a message and an optional action. + * @param crashReporter An optional crash reporter to report exceptions (default is FirebaseCrashReporter). + * @param content A composable function that defines the UI content based on the state data. + */ @Composable fun StatefulComposable( state: UiState, onShowSnackbar: suspend (String, String?) -> Boolean, + crashReporter: CrashReporter = FirebaseCrashReporter(), content: @Composable (T) -> Unit, ) { content(state.data) @@ -54,24 +62,42 @@ fun StatefulComposable( state.error.getContentIfNotHandled()?.let { error -> LaunchedEffect(onShowSnackbar) { val report = onShowSnackbar(error.message.toString(), "REPORT") - if (report) Firebase.crashlytics.recordException(error) + if (report) crashReporter.reportException(error) } } } +/** + * Data class representing the state of the UI. + * + * @param T The type of the data. + * @property data The current data of the UI. + * @property loading A flag indicating whether the UI is in a loading state. + * @property error An event representing an error that may have occurred. + */ data class UiState( val data: T, val loading: Boolean = false, val error: OneTimeEvent = OneTimeEvent(null), ) +/** + * Extension function to update the state of a MutableStateFlow. + * + * @param T The type of the data. + * @param update A function to update the data. + */ inline fun MutableStateFlow>.updateState(update: T.() -> T) { update { UiState(update(it.data)) } } -// TODO: context params will be available after kotlin 2.2 -// until then, have to pass scope as a param -// context(ViewModel) +/** + * Extension function to update the state of a MutableStateFlow with a suspend operation. + * + * @param T The type of the data. + * @param scope The CoroutineScope to launch the operation. + * @param operation A suspend function that returns a Result of the data. + */ inline fun MutableStateFlow>.updateStateWith( scope: CoroutineScope, crossinline operation: suspend () -> Result, @@ -107,9 +133,13 @@ inline fun MutableStateFlow>.updateStateWith( } } -// TODO: context params will be available after kotlin 2.2 -// until then, have to pass scope as a param -// context(ViewModel) +/** + * Extension function to update the state of a MutableStateFlow with a suspend operation that returns Unit. + * + * @param T The type of the data. + * @param scope The CoroutineScope to launch the operation. + * @param operation A suspend function that returns a Result of Unit. + */ inline fun MutableStateFlow>.updateWith( scope: CoroutineScope, crossinline operation: suspend () -> Result, diff --git a/core/ui/src/main/kotlin/dev/atick/core/ui/utils/TextFiledData.kt b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/TextFiledData.kt index e143726aa..27ca15d18 100644 --- a/core/ui/src/main/kotlin/dev/atick/core/ui/utils/TextFiledData.kt +++ b/core/ui/src/main/kotlin/dev/atick/core/ui/utils/TextFiledData.kt @@ -16,6 +16,12 @@ package dev.atick.core.ui.utils +/** + * Data class representing the state of a text field. + * + * @property value The current value of the text field. + * @property errorMessage An optional error message associated with the text field. + */ data class TextFiledData( val value: String, val errorMessage: String? = null,