From 5b890d495ff3c8ae725988911f87d7aae5225d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Mur=C3=A7a?= Date: Mon, 17 Mar 2025 19:09:19 -0300 Subject: [PATCH] Add rest of the implementation --- app/build.gradle.kts | 4 + app/src/main/AndroidManifest.xml | 4 + .../xyz/leomurca/rickandmorty/MainActivity.kt | 11 ++ .../leomurca/rickandmorty/MainApplication.kt | 2 + .../rickandmorty/data/CharacterRepository.kt | 7 + .../rickandmorty/data/CharacterResult.kt | 6 + .../rickandmorty/data/model/Character.kt | 7 + .../data/remote/RemoteCharacterRepository.kt | 39 +++++ .../leomurca/rickandmorty/di/DataModule.kt | 25 +++ .../rickandmorty/di/DispatchersModule.kt | 30 ++++ .../leomurca/rickandmorty/di/NetworkModule.kt | 50 ++++++ .../rickandmorty/network/NetworkDataSource.kt | 8 + .../rickandmorty/network/NetworkResult.kt | 6 + .../network/model/NetworkCharacter.kt | 10 ++ .../network/model/NetworkCharacterResponse.kt | 8 + .../network/model/NetworkError.kt | 9 ++ .../rickandmorty/network/remote/ApiService.kt | 12 ++ .../network/remote/RemoteNetworkDataSource.kt | 29 ++++ .../rickandmorty/ui/home/HomeScreen.kt | 142 ++++++++++++++++++ .../rickandmorty/ui/home/HomeViewModel.kt | 39 +++++ build.gradle.kts | 1 + gradle/libs.versions.toml | 5 + 22 files changed, 454 insertions(+) create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterRepository.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterResult.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/data/model/Character.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/data/remote/RemoteCharacterRepository.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/di/DataModule.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/di/DispatchersModule.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/di/NetworkModule.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkDataSource.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkResult.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacter.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacterResponse.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkError.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/remote/ApiService.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/network/remote/RemoteNetworkDataSource.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeScreen.kt create mode 100644 app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 560fac4..2a5d27f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.compose) + id("com.google.dagger.hilt.android") } android { @@ -63,6 +64,9 @@ dependencies { implementation(libs.coil.kt.compose) implementation(libs.retrofit2.retrofit) implementation(libs.retrofit.retrofit2.kotlinx.serialization.converter) + implementation(libs.hilt.android) + implementation (libs.okhttp) + kapt(libs.hilt.android.compiler) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 021b8f8..f2bfd54 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,11 @@ + + + + HomeScreen(viewModel) } } } diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/MainApplication.kt b/app/src/main/java/xyz/leomurca/rickandmorty/MainApplication.kt index 54c6c6e..9f04f77 100644 --- a/app/src/main/java/xyz/leomurca/rickandmorty/MainApplication.kt +++ b/app/src/main/java/xyz/leomurca/rickandmorty/MainApplication.kt @@ -1,5 +1,7 @@ package xyz.leomurca.rickandmorty import android.app.Application +import dagger.hilt.android.HiltAndroidApp +@HiltAndroidApp class MainApplication : Application() \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterRepository.kt b/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterRepository.kt new file mode 100644 index 0000000..1dd9556 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterRepository.kt @@ -0,0 +1,7 @@ +package xyz.leomurca.rickandmorty.data + +import xyz.leomurca.rickandmorty.data.model.Character + +interface CharacterRepository { + suspend fun characters(): CharacterResult> +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterResult.kt b/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterResult.kt new file mode 100644 index 0000000..072c2f8 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/data/CharacterResult.kt @@ -0,0 +1,6 @@ +package xyz.leomurca.rickandmorty.data + +sealed class CharacterResult { + data class Success(val data: T) : CharacterResult() + data class Error(val message: String) : CharacterResult() +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/data/model/Character.kt b/app/src/main/java/xyz/leomurca/rickandmorty/data/model/Character.kt new file mode 100644 index 0000000..ae4201b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/data/model/Character.kt @@ -0,0 +1,7 @@ +package xyz.leomurca.rickandmorty.data.model + +data class Character( + val id: Int, + val name: String, + val image: String +) diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/data/remote/RemoteCharacterRepository.kt b/app/src/main/java/xyz/leomurca/rickandmorty/data/remote/RemoteCharacterRepository.kt new file mode 100644 index 0000000..7e4dbed --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/data/remote/RemoteCharacterRepository.kt @@ -0,0 +1,39 @@ +package xyz.leomurca.rickandmorty.data.remote + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import xyz.leomurca.rickandmorty.data.CharacterRepository +import xyz.leomurca.rickandmorty.data.CharacterResult +import xyz.leomurca.rickandmorty.data.model.Character +import xyz.leomurca.rickandmorty.di.AppDispatchers +import xyz.leomurca.rickandmorty.di.Dispatcher +import xyz.leomurca.rickandmorty.network.NetworkDataSource +import xyz.leomurca.rickandmorty.network.NetworkResult + +class RemoteCharacterRepository( + @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, + private val dataSource: NetworkDataSource, +) : CharacterRepository { + override suspend fun characters(): CharacterResult> { + return withContext(ioDispatcher) { + when (val result = dataSource.characters()) { + is NetworkResult.Success -> { + CharacterResult.Success( + result.data.results.map { + Character( + id = it.id, + name = it.name, + image = it.image + ) + } + ) + } + + is NetworkResult.Error -> { + CharacterResult.Error(result.errorMessage) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/di/DataModule.kt b/app/src/main/java/xyz/leomurca/rickandmorty/di/DataModule.kt new file mode 100644 index 0000000..3cada2c --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/di/DataModule.kt @@ -0,0 +1,25 @@ +package xyz.leomurca.rickandmorty.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher +import xyz.leomurca.rickandmorty.data.CharacterRepository +import xyz.leomurca.rickandmorty.data.remote.RemoteCharacterRepository +import xyz.leomurca.rickandmorty.network.NetworkDataSource +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class DataModule { + + @Provides + @Singleton + fun providesMovieRepository( + @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, + dataSource: NetworkDataSource, + ): CharacterRepository { + return RemoteCharacterRepository(ioDispatcher, dataSource) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/di/DispatchersModule.kt b/app/src/main/java/xyz/leomurca/rickandmorty/di/DispatchersModule.kt new file mode 100644 index 0000000..e290c85 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/di/DispatchersModule.kt @@ -0,0 +1,30 @@ +package xyz.leomurca.rickandmorty.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class Dispatcher(val appDispatcher: AppDispatchers) + +enum class AppDispatchers { + Default, + IO, +} + +@Module +@InstallIn(SingletonComponent::class) +object DispatchersModule { + @Provides + @Dispatcher(AppDispatchers.IO) + fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO + + @Provides + @Dispatcher(AppDispatchers.Default) + fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/di/NetworkModule.kt b/app/src/main/java/xyz/leomurca/rickandmorty/di/NetworkModule.kt new file mode 100644 index 0000000..fafb63b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/di/NetworkModule.kt @@ -0,0 +1,50 @@ +package xyz.leomurca.rickandmorty.di + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +import xyz.leomurca.rickandmorty.BuildConfig +import xyz.leomurca.rickandmorty.network.NetworkDataSource +import xyz.leomurca.rickandmorty.network.remote.ApiService +import xyz.leomurca.rickandmorty.network.remote.RemoteNetworkDataSource +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object NetworkModule { + + @Provides + @Singleton + fun providesNetworkJson(): Json = Json { + ignoreUnknownKeys = true + isLenient = true + explicitNulls = false + } + + @Provides + @Singleton + fun provideNetworkDataSource(apiService: ApiService, json: Json): NetworkDataSource { + return RemoteNetworkDataSource(apiService, json) + } + + @Provides + @Singleton + fun provideRetrofit(json: Json): Retrofit { + val contentType = "application/json".toMediaType() + return Retrofit.Builder() + .baseUrl(BuildConfig.API_BASE_URL) + .addConverterFactory(json.asConverterFactory(contentType)) + .build() + } + + @Provides + @Singleton + fun provideApiService(retrofit: Retrofit): ApiService { + return retrofit.create(ApiService::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkDataSource.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkDataSource.kt new file mode 100644 index 0000000..a94d724 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkDataSource.kt @@ -0,0 +1,8 @@ +package xyz.leomurca.rickandmorty.network + +import xyz.leomurca.rickandmorty.network.model.NetworkCharacter +import xyz.leomurca.rickandmorty.network.model.NetworkCharacterResponse + +interface NetworkDataSource { + suspend fun characters(): NetworkResult +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkResult.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkResult.kt new file mode 100644 index 0000000..7cf301b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/NetworkResult.kt @@ -0,0 +1,6 @@ +package xyz.leomurca.rickandmorty.network + +sealed class NetworkResult { + data class Success(val data: T) : NetworkResult() + data class Error(val errorMessage: String) : NetworkResult() +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacter.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacter.kt new file mode 100644 index 0000000..01045a2 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacter.kt @@ -0,0 +1,10 @@ +package xyz.leomurca.rickandmorty.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class NetworkCharacter( + val id: Int, + val name: String, + val image: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacterResponse.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacterResponse.kt new file mode 100644 index 0000000..64f3a87 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkCharacterResponse.kt @@ -0,0 +1,8 @@ +package xyz.leomurca.rickandmorty.network.model + +import kotlinx.serialization.Serializable + +@Serializable +data class NetworkCharacterResponse( + val results: List, +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkError.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkError.kt new file mode 100644 index 0000000..96047b9 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/model/NetworkError.kt @@ -0,0 +1,9 @@ +package xyz.leomurca.rickandmorty.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NetworkError( + @SerialName("error") val message: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/ApiService.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/ApiService.kt new file mode 100644 index 0000000..19d70f3 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/ApiService.kt @@ -0,0 +1,12 @@ +package xyz.leomurca.rickandmorty.network.remote + +import retrofit2.Response +import retrofit2.http.GET +import xyz.leomurca.rickandmorty.network.model.NetworkCharacter +import xyz.leomurca.rickandmorty.network.model.NetworkCharacterResponse + +interface ApiService { + + @GET("character") + suspend fun characters(): Response +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/RemoteNetworkDataSource.kt b/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/RemoteNetworkDataSource.kt new file mode 100644 index 0000000..9b5319a --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/network/remote/RemoteNetworkDataSource.kt @@ -0,0 +1,29 @@ +package xyz.leomurca.rickandmorty.network.remote + +import kotlinx.serialization.json.Json +import xyz.leomurca.rickandmorty.network.NetworkDataSource +import xyz.leomurca.rickandmorty.network.NetworkResult +import xyz.leomurca.rickandmorty.network.model.NetworkCharacter +import xyz.leomurca.rickandmorty.network.model.NetworkCharacterResponse +import xyz.leomurca.rickandmorty.network.model.NetworkError + +class RemoteNetworkDataSource( + private val apiService: ApiService, + private val json: Json +): NetworkDataSource { + override suspend fun characters(): NetworkResult { + return try { + val response = apiService.characters() + + if (response.isSuccessful) { + NetworkResult.Success(response.body() ?: throw Exception("Empty response body")) + } else { + val errorBody = response.errorBody()?.string() ?: "" + val networkError = json.decodeFromString(errorBody) + NetworkResult.Error(networkError.message) + } + } catch (e: Exception) { + NetworkResult.Error(e.message.toString()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeScreen.kt b/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeScreen.kt new file mode 100644 index 0000000..1be9451 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeScreen.kt @@ -0,0 +1,142 @@ +package xyz.leomurca.rickandmorty.ui.home + +import android.util.Log +import xyz.leomurca.rickandmorty.data.model.Character as Character +import android.widget.Space +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.material3.Card +import androidx.compose.material3.CardElevation +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade + +@Composable +fun HomeScreen(viewModel: HomeViewModel) { + val state = viewModel.uiState.collectAsState() + + when (val value = state.value) { + is HomeViewModel.UiState.Loading -> CharacterCardSkeleton() + is HomeViewModel.UiState.Loaded.Success -> CharacterStaggeredGrid(value.characters) + is HomeViewModel.UiState.Loaded.Error -> Text(value.message) + } +} + +@Composable +fun CharacterStaggeredGrid(characters: List) { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(minSize = 150.dp), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + items(characters.size) { index -> + CharacterCard(character = characters[index]) + } + } +} + +@Composable +fun CharacterCard(character: Character) { + Card( + modifier = Modifier.fillMaxWidth() + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(8.dp) + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(character.image) + .crossfade(true) + .build(), + contentDescription = character.name, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + ) + + Spacer(modifier = Modifier.height(8.dp)) + Text(text = character.name) + } + } +} + +@Composable +fun CharacterCardSkeleton() { + val shimmerColors = listOf( + Color.LightGray.copy(alpha = 0.6f), + Color.LightGray.copy(alpha = 0.2f), + Color.LightGray.copy(alpha = 0.6f), + ) + + val transition = rememberInfiniteTransition() + val translateAnim = transition.animateFloat( + initialValue = 0f, + targetValue = 1000f, + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = 1000, + easing = LinearEasing + ) + ) + ) + + val brush = Brush.linearGradient( + colors = shimmerColors, + start = Offset.Zero, + end = Offset(x = translateAnim.value, y = translateAnim.value) + ) + + Card( + modifier = Modifier.fillMaxWidth(), + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(8.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + .background(brush = brush, shape = RectangleShape) + ) + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .width(80.dp) + .height(16.dp) + .background(brush = brush, shape = RectangleShape) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeViewModel.kt b/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..9078dc9 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/rickandmorty/ui/home/HomeViewModel.kt @@ -0,0 +1,39 @@ +package xyz.leomurca.rickandmorty.ui.home + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import xyz.leomurca.rickandmorty.data.CharacterRepository +import xyz.leomurca.rickandmorty.data.CharacterResult +import xyz.leomurca.rickandmorty.data.model.Character +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val characterRepository: CharacterRepository +) : ViewModel() { + + private val _uiState = MutableStateFlow(UiState.Loading) + val uiState = _uiState.asStateFlow() + + init { + viewModelScope.launch { + _uiState.value = when (val result = characterRepository.characters()) { + is CharacterResult.Success -> UiState.Loaded.Success(result.data) + is CharacterResult.Error -> UiState.Loaded.Error(result.message) + } + } + } + + sealed interface UiState { + data object Loading : UiState + + sealed class Loaded : UiState { + data class Success(val characters: List) : Loaded() + data class Error(val message: String) : Loaded() + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5ba8ae0..74aae2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,4 +4,5 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.serialization) apply false + id("com.google.dagger.hilt.android") version "2.51.1" apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 136ab71..6ebabd2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.8.2" +hiltAndroid = "2.51.1" kotlin = "2.0.20" coreKtx = "1.15.0" junit = "4.13.2" @@ -11,11 +12,14 @@ composeBom = "2025.03.00" navigation = "2.8.9" kotlinxSerializationJson = "1.8.0" coil = "3.1.0" +okhttp = "4.12.0" retrofit = "1.0.0" retrofit2 = "2.11.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" } +hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -33,6 +37,7 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } coil-kt = { group = "io.coil-kt.coil3", name = "coil", version.ref = "coil" } coil-kt-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } retrofit2-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" } retrofit-retrofit2-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofit" }