diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6598314..3205221 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,11 +1,13 @@ import com.android.sdklib.AndroidVersion.ApiBaseExtension.BAKLAVA import com.android.sdklib.AndroidVersion.ApiBaseExtension.VANILLA_ICE_CREAM import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.util.Properties plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) alias(libs.plugins.hilt.android) alias(libs.plugins.ksp) } @@ -22,6 +24,8 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "API_BASE_URL", "\"${getSecret("API_BASE_URL")}\"") + buildConfigField("String", "ACCESS_TOKEN", "\"${getSecret("ACCESS_TOKEN")}\"") } buildTypes { @@ -45,6 +49,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -61,6 +66,10 @@ dependencies { implementation(libs.hilt.android) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.core.splashscreen) + implementation(libs.retrofit2) + implementation(libs.retrofit.kotlinx.serialization.converter) + implementation(libs.kotlinx.serialization.json) + implementation(libs.okhttp) ksp(libs.hilt.android.compiler) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) @@ -69,4 +78,23 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) -} \ No newline at end of file +} + +fun getSecret(key: String): String { + // 1. Check Gradle -P property (CI) + project.findProperty(key)?.let { return it.toString() } + + // 2. Check environment variable (CI) + System.getenv(key)?.let { return it } + + // 3. Fallback to local.properties (local dev) + val localPropsFile = rootProject.file("local.properties") + if (localPropsFile.exists()) { + val props = Properties().apply { + load(localPropsFile.inputStream()) + } + return props.getProperty(key) ?: "" + } + + return "" +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f25b437..df133b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ + + > { + return Resource.Success( + data = listOf( + MatchDto( + beginAt = "2025-07-27T10:30:00Z", + opponents = emptyList(), + league = LeagueDto( + id = 5078, + name = "United21", + imageUrl = "https://cdn.pandascore.co/images/league/image/5078/800px-united21_allmode-png" + ), + serie = SerieDto( + id = 9519, + fullName = "Season 35 2025" + ), + status = "not_started" + ), + MatchDto( + beginAt = "2025-07-21T10:30:00Z", + opponents = listOf( + OpponentDto( + type = "team", + opponent = OpponentRecord( + id = 128519, + name = "GenOne", + imageUrl = "https://cdn.pandascore.co/images/team/image/128519/genone_csgo.png" + ) + ), + OpponentDto( + type = "team", + opponent = OpponentRecord( + id = 134996, + name = "VOLT", + imageUrl = "https://cdn.pandascore.co/images/team/image/134996/127px_volt_2024_allmode.png" + ) + ) + ), + league = LeagueDto( + id = 5078, + name = "United21", + imageUrl = "https://cdn.pandascore.co/images/league/image/5078/800px-united21_allmode-png" + ), + serie = SerieDto( + id = 9519, + fullName = "Season 35 2025" + ), + status = "not_started" + ), + + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt new file mode 100644 index 0000000..ea7d93b --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/mapper/MatchMapper.kt @@ -0,0 +1,30 @@ +package xyz.leomurca.csgomatches.data.mapper + +import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.domain.model.League +import xyz.leomurca.csgomatches.domain.model.Match +import xyz.leomurca.csgomatches.domain.model.Opponent +import xyz.leomurca.csgomatches.domain.model.Serie + +fun MatchDto.toDomain(): Match { + return Match( + beginAt = beginAt ?: "", + opponents = opponents.map { op -> + Opponent( + id = op.opponent.id, + name = op.opponent.name ?: "", + imageUrl = op.opponent.imageUrl ?: "" + ) + }, + league = League( + id = league.id, + name = league.name ?: "", + imageUrl = league.imageUrl ?: "" + ), + serie = Serie( + id = serie.id, + name = serie.fullName ?: "" + ), + status = status ?: "" + ) +} diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/ErrorDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/ErrorDto.kt new file mode 100644 index 0000000..03add83 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/ErrorDto.kt @@ -0,0 +1,9 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorDto( + @SerialName("error") val message: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/LeagueDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/LeagueDto.kt new file mode 100644 index 0000000..41b4c2c --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/LeagueDto.kt @@ -0,0 +1,21 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LeagueDto( + val id: Long, + val name: String?, + @SerialName("image_url") + val imageUrl: String? +) + +//"league": { +// "id": 5078, +// "name": "United21", +// "url": null, +// "slug": "cs-go-united21", +// "modified_at": "2023-12-22T16:36:10Z", +// "image_url": "https://cdn.pandascore.co/images/league/image/5078/800px-united21_allmode-png" +//}, diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/MatchDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/MatchDto.kt new file mode 100644 index 0000000..9152ab6 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/MatchDto.kt @@ -0,0 +1,17 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class MatchDto( + @SerialName("begin_at") + val beginAt: String?, + val opponents: List, + val league: LeagueDto, + val serie: SerieDto, + val status: String? +) + + + diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/OpponentDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/OpponentDto.kt new file mode 100644 index 0000000..735f457 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/OpponentDto.kt @@ -0,0 +1,31 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class OpponentDto( + val type: String?, + val opponent: OpponentRecord +) + +@Serializable +data class OpponentRecord( + val id: Long, + val name: String?, + @SerialName("image_url") + val imageUrl: String? +) + +//{ +// "type": "Team", +// "opponent": { +// "id": 126694, +// "name": "BIG Academy", +// "location": "DE", +// "slug": "big-academy", +// "modified_at": "2025-07-17T10:49:15Z", +// "acronym": "BIG.A", +// "image_url": "https://cdn.pandascore.co/images/team/image/126694/big.png" +// } +//} diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/model/SerieDto.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/model/SerieDto.kt new file mode 100644 index 0000000..5c8f8de --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/model/SerieDto.kt @@ -0,0 +1,26 @@ +package xyz.leomurca.csgomatches.data.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SerieDto( + val id: Long, + @SerialName("full_name") + val fullName: String? +) + +//"serie": { +// "id": 9519, +// "name": "", +// "year": 2025, +// "begin_at": "2025-07-18T08:00:00Z", +// "end_at": "2025-08-04T21:00:00Z", +// "winner_id": null, +// "winner_type": "Team", +// "slug": "cs-go-united21-35-2025", +// "modified_at": "2025-07-17T10:55:39Z", +// "league_id": 5078, +// "season": "35", +// "full_name": "Season 35 2025" +//}, diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/AuthorizationInterceptor.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/AuthorizationInterceptor.kt new file mode 100644 index 0000000..31a8e18 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/AuthorizationInterceptor.kt @@ -0,0 +1,15 @@ +package xyz.leomurca.csgomatches.data.remote + +import okhttp3.Interceptor +import okhttp3.Response + +class AuthorizationInterceptor( + private val token: String +) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer $token") + .build() + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt new file mode 100644 index 0000000..f7c456f --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchRemoteDataSourceImpl.kt @@ -0,0 +1,28 @@ +package xyz.leomurca.csgomatches.data.remote + +import kotlinx.serialization.json.Json +import xyz.leomurca.csgomatches.data.model.ErrorDto +import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.data.source.MatchDataSource +import xyz.leomurca.csgomatches.domain.model.Resource + +class MatchRemoteDataSourceImpl( + private val matchesApiService: MatchesApiService, + private val json: Json +) : MatchDataSource { + override suspend fun upcomingMatches(): Resource> { + return try { + val response = matchesApiService.upcomingMatches() + + if (response.isSuccessful) { + Resource.Success(response.body() ?: throw Exception("Empty response body")) + } else { + val errorBody = response.errorBody()?.string() ?: "" + val networkError = json.decodeFromString(errorBody) + Resource.Error(networkError.message) + } + } catch (e: Exception) { + Resource.Error(e.message.toString()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt new file mode 100644 index 0000000..3c4aad9 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/remote/MatchesApiService.kt @@ -0,0 +1,11 @@ +package xyz.leomurca.csgomatches.data.remote + +import retrofit2.Response +import retrofit2.http.GET +import xyz.leomurca.csgomatches.data.model.MatchDto + +interface MatchesApiService { + + @GET("matches") + suspend fun upcomingMatches(): Response> +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt new file mode 100644 index 0000000..64b6dd5 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/repository/MatchRepositoryImpl.kt @@ -0,0 +1,25 @@ +package xyz.leomurca.csgomatches.data.repository + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import xyz.leomurca.csgomatches.data.mapper.toDomain +import xyz.leomurca.csgomatches.data.source.MatchDataSource +import xyz.leomurca.csgomatches.di.AppDispatchers +import xyz.leomurca.csgomatches.di.Dispatcher +import xyz.leomurca.csgomatches.domain.model.Match +import xyz.leomurca.csgomatches.domain.model.Resource +import xyz.leomurca.csgomatches.domain.repository.MatchRepository + +class MatchRepositoryImpl( + @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, + private val matchRemoteDataSource: MatchDataSource +) : MatchRepository { + override suspend fun upcomingMatches(): Resource> { + return withContext(ioDispatcher) { + when (val result = matchRemoteDataSource.upcomingMatches()) { + is Resource.Success -> Resource.Success(result.data.map { it.toDomain() }) + is Resource.Error -> Resource.Error(result.message) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt b/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt new file mode 100644 index 0000000..676cc5d --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/data/source/MatchDataSource.kt @@ -0,0 +1,8 @@ +package xyz.leomurca.csgomatches.data.source + +import xyz.leomurca.csgomatches.data.model.MatchDto +import xyz.leomurca.csgomatches.domain.model.Resource + +interface MatchDataSource { + suspend fun upcomingMatches(): Resource> +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/di/DispatchersModule.kt b/app/src/main/java/xyz/leomurca/csgomatches/di/DispatchersModule.kt new file mode 100644 index 0000000..aead0ae --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/di/DispatchersModule.kt @@ -0,0 +1,30 @@ +package xyz.leomurca.csgomatches.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import jakarta.inject.Qualifier +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +@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/csgomatches/di/NetworkModule.kt b/app/src/main/java/xyz/leomurca/csgomatches/di/NetworkModule.kt new file mode 100644 index 0000000..0a63b70 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/di/NetworkModule.kt @@ -0,0 +1,89 @@ +package xyz.leomurca.csgomatches.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.coroutines.CoroutineDispatcher +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import retrofit2.Retrofit +import xyz.leomurca.csgomatches.data.remote.MatchesApiService +import javax.inject.Singleton +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import xyz.leomurca.csgomatches.BuildConfig +import xyz.leomurca.csgomatches.data.local.MatchLocalDataSource +import xyz.leomurca.csgomatches.data.remote.AuthorizationInterceptor +import xyz.leomurca.csgomatches.data.remote.MatchRemoteDataSourceImpl +import xyz.leomurca.csgomatches.data.repository.MatchRepositoryImpl +import xyz.leomurca.csgomatches.data.source.MatchDataSource +import xyz.leomurca.csgomatches.domain.repository.MatchRepository + +@Module +@InstallIn(SingletonComponent::class) +internal object NetworkModule { + + @Provides + @Singleton + fun providesNetworkJson(): Json = Json { + ignoreUnknownKeys = true + isLenient = true + explicitNulls = false + } + + @Provides + @Singleton + fun provideAuthorizationInterceptor(): Interceptor { + return AuthorizationInterceptor(BuildConfig.ACCESS_TOKEN) + } + + @Provides + @Singleton + fun provideOkHttpClient(authInterceptor: Interceptor): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(json: Json, okHttpClient: OkHttpClient): Retrofit { + val contentType = "application/json".toMediaType() + return Retrofit.Builder() + .baseUrl(BuildConfig.API_BASE_URL) + .client(okHttpClient) + .addConverterFactory(json.asConverterFactory(contentType)) + .build() + } + + @Provides + @Singleton + fun provideMatchesApiService(retrofit: Retrofit): MatchesApiService { + return retrofit.create(MatchesApiService::class.java) + } + + @Provides + @Singleton + fun provideMatchRemoteDataSource( + matchesApiService: MatchesApiService, + json: Json + ): MatchDataSource { + val useRemote = false + return if (useRemote) { + MatchRemoteDataSourceImpl(matchesApiService, json) + } else { + MatchLocalDataSource() + } + } + + @Provides + @Singleton + fun providesMatchRepository( + @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, + matchRemoteDataSource: MatchDataSource + ): MatchRepository { + return MatchRepositoryImpl(ioDispatcher, matchRemoteDataSource) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/League.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/League.kt new file mode 100644 index 0000000..23cd46c --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/League.kt @@ -0,0 +1,7 @@ +package xyz.leomurca.csgomatches.domain.model + +data class League( + val id: Long, + val name: String, + val imageUrl: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt new file mode 100644 index 0000000..d1e017d --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Match.kt @@ -0,0 +1,10 @@ +package xyz.leomurca.csgomatches.domain.model + + +data class Match( + val beginAt: String, + val opponents: List, + val league: League, + val serie: Serie, + val status: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Opponent.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Opponent.kt new file mode 100644 index 0000000..cda1d71 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Opponent.kt @@ -0,0 +1,7 @@ +package xyz.leomurca.csgomatches.domain.model + +data class Opponent( + val id: Long, + val name: String, + val imageUrl: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Resource.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Resource.kt new file mode 100644 index 0000000..7b6f998 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Resource.kt @@ -0,0 +1,6 @@ +package xyz.leomurca.csgomatches.domain.model + +sealed class Resource { + data class Success(val data: T) : Resource() + data class Error(val message: String) : Resource() +} \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Serie.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Serie.kt new file mode 100644 index 0000000..1d3b1ef --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/model/Serie.kt @@ -0,0 +1,6 @@ +package xyz.leomurca.csgomatches.domain.model + +data class Serie( + val id: Long, + val name: String +) \ No newline at end of file diff --git a/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt b/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt new file mode 100644 index 0000000..c148c22 --- /dev/null +++ b/app/src/main/java/xyz/leomurca/csgomatches/domain/repository/MatchRepository.kt @@ -0,0 +1,9 @@ +package xyz.leomurca.csgomatches.domain.repository + +import xyz.leomurca.csgomatches.domain.model.Match +import xyz.leomurca.csgomatches.domain.model.Resource + +interface MatchRepository { + + suspend fun upcomingMatches(): Resource> +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4e956bb..a01e24a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.hilt.android) apply false alias(libs.plugins.ksp) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f140eed..2181ece 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,10 @@ hilt = "2.56.2" ksp = "2.2.0-2.0.2" navigation = "2.9.2" splashScreen = "1.0.1" +retrofit = "1.0.0" +retrofit2 = "2.11.0" +kotlinxSerializationJson = "1.8.1" +okhttp = "4.12.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -28,10 +32,14 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } -hilt-android = { group = "com.google.dagger", name="hilt-android", version.ref = "hilt" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splashScreen" } +retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" } +retrofit-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofit" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -39,5 +47,6 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }