diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d33521..8f00030d 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,5 @@ # Default ignored files /shelf/ /workspace.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/core/common/src/main/java/dev/enesky/core/common/consts/ErrorMessages.kt b/core/common/src/main/java/dev/enesky/core/common/consts/ErrorMessages.kt index aaa583b7..745ed526 100644 --- a/core/common/src/main/java/dev/enesky/core/common/consts/ErrorMessages.kt +++ b/core/common/src/main/java/dev/enesky/core/common/consts/ErrorMessages.kt @@ -10,3 +10,15 @@ object ErrorMessages { const val GENERAL_ERROR = "An error occurred. Please try again later." const val NO_INTERNET_CONNECTION = "No internet connection. Please try again later." } + +/** + * General error handler + * @param throwable: Throwable + */ +fun getErrorMessage(throwable: Throwable?): String { + return when { + throwable?.localizedMessage != null -> throwable.localizedMessage.toString() + throwable?.message != null -> throwable.message.toString() + else -> ErrorMessages.GENERAL_ERROR + } +} diff --git a/core/common/src/main/java/dev/enesky/core/common/delegate/EventChannelDelegate.kt b/core/common/src/main/java/dev/enesky/core/common/delegate/EventChannelDelegate.kt index 38656234..ed1c6ba6 100644 --- a/core/common/src/main/java/dev/enesky/core/common/delegate/EventChannelDelegate.kt +++ b/core/common/src/main/java/dev/enesky/core/common/delegate/EventChannelDelegate.kt @@ -13,6 +13,10 @@ import kotlinx.coroutines.flow.receiveAsFlow */ interface IEvent +interface IErrorEvent : IEvent { + val errorMessage: String +} + /** * Base interface for event delegation */ @@ -46,7 +50,7 @@ interface Event { * * @check ObserveAsEvents in core/common/src/main/java/dev/enesky/core/common/utils/ObserveAsEvents.kt */ -class EventDelegate : Event { +class EventDelegate : Event { override val event: Channel = Channel() override val eventFlow: Flow = event.receiveAsFlow() diff --git a/core/common/src/main/java/dev/enesky/core/common/delegate/UiStateDelegate.kt b/core/common/src/main/java/dev/enesky/core/common/delegate/UiStateDelegate.kt index eee640f8..fa96afab 100644 --- a/core/common/src/main/java/dev/enesky/core/common/delegate/UiStateDelegate.kt +++ b/core/common/src/main/java/dev/enesky/core/common/delegate/UiStateDelegate.kt @@ -11,8 +11,7 @@ import kotlinx.coroutines.flow.StateFlow * Base interface for defining new UiState data classes **/ interface IUiState { - val loading: Boolean - var errorMessage: String? + val isLoading: Boolean } /** diff --git a/feature/details/src/main/java/dev/enesky/feature/details/DetailsScreen.kt b/feature/details/src/main/java/dev/enesky/feature/details/DetailsScreen.kt index 8f05212a..b3d51723 100644 --- a/feature/details/src/main/java/dev/enesky/feature/details/DetailsScreen.kt +++ b/feature/details/src/main/java/dev/enesky/feature/details/DetailsScreen.kt @@ -47,22 +47,10 @@ fun DetailsRoute( viewModel.getThemAll(animeId = animeId.toInt()) } - LaunchedEffect(key1 = uiState.errorMessage) { - if (uiState.errorMessage.isNullOrEmpty().not()) { - viewModel.triggerEvent { - DetailsEvents.OnError(uiState.errorMessage ?: String.Empty) - } - } - } - ObserveAsEvents(flow = viewModel.eventFlow) { event -> when (event) { - is DetailsEvents.OnError -> { - onShowMessage(event.errorMessage) - uiState.errorMessage = null - } - is DetailsEvents.OnTrailerPlayClick -> { - } + is DetailsEvents.OnError -> onShowMessage(event.errorMessage) + is DetailsEvents.OnTrailerPlayClick -> { } } } @@ -82,7 +70,7 @@ private fun DetailsScreen( ) { SwipeRefresh( modifier = modifier, - isRefreshing = uiState.loading, + isRefreshing = uiState.isLoading, onRefresh = onRefresh, ) { val listState = rememberLazyListState() @@ -99,19 +87,19 @@ private fun DetailsScreen( item { DetailedAnimePreview( detailedAnime = uiState.detailedAnime, - isLoading = uiState.loading, + isLoading = uiState.isLoading, ) } item { DetailedAnimeSummary( summary = uiState.detailedAnime?.summary ?: String.Empty, - isLoading = uiState.loading, + isLoading = uiState.isLoading, ) } item { AnimeCharactersRow( animeCharacters = uiState.characters, - isLoading = uiState.loading, + isLoading = uiState.isLoading, ) } item { @@ -120,7 +108,7 @@ private fun DetailsScreen( item { AnimeRecommendationsRow( animeRecommendations = uiState.recommendations, - isLoading = uiState.loading, + isLoading = uiState.isLoading, ) } } diff --git a/feature/details/src/main/java/dev/enesky/feature/details/DetailsViewModel.kt b/feature/details/src/main/java/dev/enesky/feature/details/DetailsViewModel.kt index 9f22122c..d569fe4b 100644 --- a/feature/details/src/main/java/dev/enesky/feature/details/DetailsViewModel.kt +++ b/feature/details/src/main/java/dev/enesky/feature/details/DetailsViewModel.kt @@ -3,6 +3,7 @@ package dev.enesky.feature.details import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn +import dev.enesky.core.common.consts.getErrorMessage import dev.enesky.core.common.delegate.Event import dev.enesky.core.common.delegate.EventDelegate import dev.enesky.core.common.delegate.UiState @@ -46,19 +47,26 @@ class DetailsViewModel( detailedAnimeUseCase(animeId = animeId) .asResult() .onEach { resource -> - updateUiState { - when (resource) { - is Result.Loading -> copy(loading = true) - is Result.Success -> copy( - loading = false, + when (resource) { + is Result.Loading -> updateUiState { copy(isLoading = true) } + is Result.Success -> updateUiState { + copy( + isLoading = false, detailedAnime = resource.data, ) - - is Result.Error -> copy( - loading = false, - detailedAnime = null, - errorMessage = resource.exception?.message, - ) + } + is Result.Error -> { + updateUiState { + copy( + isLoading = false, + detailedAnime = null, + ) + } + triggerEvent { + DetailsEvents.OnError( + getErrorMessage(resource.exception) + ) + } } } } @@ -71,19 +79,26 @@ class DetailsViewModel( animeCharactersUseCase(animeId = animeId) .asResult() .onEach { resource -> - updateUiState { - when (resource) { - is Result.Loading -> copy(loading = true) - is Result.Success -> copy( - loading = false, + when (resource) { + is Result.Loading -> updateUiState { copy(isLoading = true) } + is Result.Success -> updateUiState { + copy( + isLoading = false, characters = resource.data, ) - - is Result.Error -> copy( - loading = false, - characters = null, - errorMessage = resource.exception?.message, - ) + } + is Result.Error -> { + updateUiState { + copy( + isLoading = false, + characters = null, + ) + } + triggerEvent { + DetailsEvents.OnError( + getErrorMessage(resource.exception) + ) + } } } } @@ -100,7 +115,7 @@ class DetailsViewModel( updateUiState { copy( - loading = false, + isLoading = false, episodes = popularAnimesFlow, ) } @@ -111,19 +126,26 @@ class DetailsViewModel( animeRecommendationsUseCase(animeId = animeId) .asResult() .onEach { resource -> - updateUiState { - when (resource) { - is Result.Loading -> copy(loading = true) - is Result.Success -> copy( - loading = false, + when (resource) { + is Result.Loading -> updateUiState { copy(isLoading = true) } + is Result.Success -> updateUiState { + copy( + isLoading = false, recommendations = resource.data, ) - - is Result.Error -> copy( - loading = false, - recommendations = null, - errorMessage = resource.exception?.message, - ) + } + is Result.Error -> { + updateUiState { + copy( + isLoading = false, + recommendations = null, + ) + } + triggerEvent { + DetailsEvents.OnError( + getErrorMessage(resource.exception) + ) + } } } } diff --git a/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsEvents.kt b/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsEvents.kt index 98373606..70015bb5 100644 --- a/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsEvents.kt +++ b/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsEvents.kt @@ -1,5 +1,6 @@ package dev.enesky.feature.details.helpers +import dev.enesky.core.common.delegate.IErrorEvent import dev.enesky.core.common.delegate.IEvent /** @@ -7,6 +8,6 @@ import dev.enesky.core.common.delegate.IEvent */ sealed interface DetailsEvents : IEvent { - data class OnError(val errorMessage: String) : DetailsEvents + data class OnError(override val errorMessage: String) : DetailsEvents, IErrorEvent data class OnTrailerPlayClick(val animeId: String) : DetailsEvents } diff --git a/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsUiState.kt b/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsUiState.kt index 2d7fe2db..c53c89cb 100644 --- a/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsUiState.kt +++ b/feature/details/src/main/java/dev/enesky/feature/details/helpers/DetailsUiState.kt @@ -13,8 +13,7 @@ import kotlinx.coroutines.flow.Flow */ data class DetailsUiState( - override val loading: Boolean = false, - override var errorMessage: String? = null, + override val isLoading: Boolean = false, val detailedAnime: DetailedAnime? = null, val characters: List? = null, val episodes: Flow>? = null, diff --git a/feature/home/src/main/java/dev/enesky/feature/home/HomeScreen.kt b/feature/home/src/main/java/dev/enesky/feature/home/HomeScreen.kt index 4dd0e01c..c513861d 100644 --- a/feature/home/src/main/java/dev/enesky/feature/home/HomeScreen.kt +++ b/feature/home/src/main/java/dev/enesky/feature/home/HomeScreen.kt @@ -35,10 +35,6 @@ fun HomeRoute( ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - if (uiState.errorMessage != null) { - onShowMessage(uiState.errorMessage.toString()) - } - ObserveAsEvents(flow = viewModel.eventFlow) { homeEvents -> when (homeEvents) { is HomeEvents.OnError -> onShowMessage(homeEvents.errorMessage) @@ -94,7 +90,7 @@ fun HomeScreen( item { AnimePreview( anime = uiState.previewAnime, - isLoading = uiState.loading, + isLoading = uiState.isLoading, onNavigateDetailsClick = onNavigateDetailsClick, ) } diff --git a/feature/home/src/main/java/dev/enesky/feature/home/HomeViewModel.kt b/feature/home/src/main/java/dev/enesky/feature/home/HomeViewModel.kt index 990c61ca..b6571569 100644 --- a/feature/home/src/main/java/dev/enesky/feature/home/HomeViewModel.kt +++ b/feature/home/src/main/java/dev/enesky/feature/home/HomeViewModel.kt @@ -3,6 +3,8 @@ package dev.enesky.feature.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.cachedIn +import dev.enesky.core.common.consts.ErrorMessages +import dev.enesky.core.common.consts.getErrorMessage import dev.enesky.core.common.delegate.Event import dev.enesky.core.common.delegate.EventDelegate import dev.enesky.core.common.delegate.UiState @@ -62,7 +64,7 @@ class HomeViewModel( updateUiState { copy( - loading = false, + isLoading = false, popularAnimes = popularAnimesFlow, airingAnimes = airingAnimesFlow, upcomingAnimes = upcomingAnimesFlow, @@ -80,7 +82,7 @@ class HomeViewModel( is Result.Loading -> { updateUiState { copy( - loading = true, + isLoading = true, previewAnime = null, ) } @@ -90,7 +92,7 @@ class HomeViewModel( if (resource.data.id == 0) { updateUiState { copy( - loading = false, + isLoading = false, previewAnime = null, ) } @@ -98,7 +100,7 @@ class HomeViewModel( } updateUiState { copy( - loading = false, + isLoading = false, previewAnime = resource.data, ) } @@ -107,10 +109,15 @@ class HomeViewModel( is Result.Error -> { updateUiState { copy( - loading = false, + isLoading = false, previewAnime = null, ) } + triggerEvent { + HomeEvents.OnError( + getErrorMessage(resource.exception) + ) + } } } } diff --git a/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeEvents.kt b/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeEvents.kt index b1142519..96f2b850 100644 --- a/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeEvents.kt +++ b/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeEvents.kt @@ -1,5 +1,6 @@ package dev.enesky.feature.home.helpers +import dev.enesky.core.common.delegate.IErrorEvent import dev.enesky.core.common.delegate.IEvent /** @@ -7,7 +8,7 @@ import dev.enesky.core.common.delegate.IEvent */ sealed interface HomeEvents : IEvent { - data class OnError(val errorMessage: String) : HomeEvents + data class OnError(override val errorMessage: String) : HomeEvents, IErrorEvent data class OnItemOptionsClick(val animeId: String) : HomeEvents data class NavigateToDetails(val animeId: String) : HomeEvents } diff --git a/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeUiState.kt b/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeUiState.kt index 4c391f2a..cb42bee0 100644 --- a/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeUiState.kt +++ b/feature/home/src/main/java/dev/enesky/feature/home/helpers/HomeUiState.kt @@ -11,8 +11,7 @@ import kotlinx.coroutines.flow.Flow */ data class HomeUiState( - override val loading: Boolean = false, - override var errorMessage: String? = null, + override val isLoading: Boolean = false, val airingAnimes: Flow>? = null, val upcomingAnimes: Flow>? = null, val popularAnimes: Flow>? = null, diff --git a/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInEvents.kt b/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInEvents.kt index 769f7587..ce19db19 100644 --- a/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInEvents.kt +++ b/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInEvents.kt @@ -1,5 +1,6 @@ package dev.enesky.feature.login.signin.helpers +import dev.enesky.core.common.delegate.IErrorEvent import dev.enesky.core.common.delegate.IEvent /** @@ -7,7 +8,8 @@ import dev.enesky.core.common.delegate.IEvent */ sealed interface SignInEvents : IEvent { - data class OnError(val errorMessage: String) : SignInEvents + data class OnError(override val errorMessage: String) : SignInEvents, IErrorEvent data object NavigateToHome : SignInEvents data object NavigateToSignUp : SignInEvents + } diff --git a/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInUiState.kt b/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInUiState.kt index f2aa10ce..0be34c4b 100644 --- a/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInUiState.kt +++ b/feature/login/src/main/java/dev/enesky/feature/login/signin/helpers/SignInUiState.kt @@ -9,8 +9,7 @@ import dev.enesky.core.domain.models.LoginResult */ data class SignInUiState( - override val loading: Boolean = false, - override var errorMessage: String? = null, + override val isLoading: Boolean = false, val loginType: LoginType = LoginType.ANONYMOUS, val loginResult: LoginResult? = null, ) : IUiState diff --git a/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpEvents.kt b/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpEvents.kt index 803c5d5b..edeb12b6 100644 --- a/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpEvents.kt +++ b/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpEvents.kt @@ -1,5 +1,6 @@ package dev.enesky.feature.login.signup.helpers +import dev.enesky.core.common.delegate.IErrorEvent import dev.enesky.core.common.delegate.IEvent /** @@ -7,7 +8,7 @@ import dev.enesky.core.common.delegate.IEvent */ sealed interface SignUpEvents : IEvent { - data class OnError(val errorMessage: String) : SignUpEvents + data class OnError(override val errorMessage: String) : SignUpEvents, IErrorEvent data object NavigateToHome : SignUpEvents data object NavigateToSignIn : SignUpEvents } diff --git a/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpUiState.kt b/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpUiState.kt index cc53c82f..7953d9d2 100644 --- a/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpUiState.kt +++ b/feature/login/src/main/java/dev/enesky/feature/login/signup/helpers/SignUpUiState.kt @@ -9,8 +9,7 @@ import dev.enesky.core.domain.models.LoginResult */ data class SignUpUiState( - override val loading: Boolean = false, - override var errorMessage: String? = null, + override val isLoading: Boolean = false, val loginType: LoginType = LoginType.EMAIL, val loginResult: LoginResult? = null, ) : IUiState diff --git a/feature/splash/src/main/java/dev/enesky/feature/splash/SplashViewModel.kt b/feature/splash/src/main/java/dev/enesky/feature/splash/SplashViewModel.kt index ba729cca..bf1fa145 100644 --- a/feature/splash/src/main/java/dev/enesky/feature/splash/SplashViewModel.kt +++ b/feature/splash/src/main/java/dev/enesky/feature/splash/SplashViewModel.kt @@ -5,11 +5,13 @@ import androidx.lifecycle.viewModelScope import dev.enesky.core.common.consts.ErrorMessages import dev.enesky.core.common.delegate.Event import dev.enesky.core.common.delegate.EventDelegate +import dev.enesky.core.common.delegate.IErrorEvent import dev.enesky.core.common.delegate.IEvent import dev.enesky.core.common.delegate.IUiState import dev.enesky.core.common.delegate.UiState import dev.enesky.core.common.delegate.UiStateDelegate import dev.enesky.core.domain.manager.AuthManager +import dev.enesky.feature.home.helpers.HomeEvents import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -52,13 +54,12 @@ class SplashViewModel( } data class SplashUiState( - override val loading: Boolean = false, - override var errorMessage: String? = null, + override val isLoading: Boolean = false, val isUserLoggedIn: Boolean? = null, ) : IUiState sealed interface SplashEvents : IEvent { - data class OnError(val errorMessage: String) : SplashEvents + data class OnError(override val errorMessage: String) : SplashEvents, IErrorEvent data object OnNavigateToLoginScreen : SplashEvents data object OnNavigateToHomeScreen : SplashEvents }