Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact
import com.yapp.ndgl.data.core.BuildConfig
import com.yapp.ndgl.data.core.adapter.NDGLCallAdapterFactory
import com.yapp.ndgl.data.core.authenticator.NDGLAuthenticator
import com.yapp.ndgl.data.core.interceptor.AndroidCredentialInterceptor
import com.yapp.ndgl.data.core.interceptor.ApiKeyInterceptor
import com.yapp.ndgl.data.core.interceptor.ApiKeyQueryInterceptor
import com.yapp.ndgl.data.core.interceptor.NDGLInterceptor
import com.yapp.ndgl.data.core.interceptor.RouteInterceptor
import dagger.Module
Expand Down Expand Up @@ -121,6 +123,28 @@ object NetworkModule {
.addInterceptor(httpLoggingInterceptor)
.build()
}

@WeatherClient
@Singleton
@Provides
fun provideWeatherOkHttpClient(
@WeatherApiKey apiKey: String,
androidCredentialInterceptor: AndroidCredentialInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(ApiKeyQueryInterceptor(apiKey))
.addInterceptor(androidCredentialInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build()

@ExchangeRateClient
@Singleton
@Provides
fun provideExchangeRateOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
}

@Qualifier
Expand All @@ -146,3 +170,23 @@ annotation class RouteBaseUrl
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RouteClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class WeatherApiKey

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class WeatherClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class GeocodingClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ExchangeRateApiKey

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class ExchangeRateClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.yapp.ndgl.data.core.interceptor

import android.content.Context
import android.content.pm.PackageManager
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.Interceptor
import okhttp3.Response
import java.security.MessageDigest
import javax.inject.Inject

@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
class AndroidCredentialInterceptor @Inject constructor(
@ApplicationContext private val context: Context,
) : Interceptor {
private val packageName: String = context.packageName
private val sha1Cert: String = computeSha1(context)

override fun intercept(chain: Interceptor.Chain): Response {
val newRequest = chain.request().newBuilder()
.addHeader("X-Android-Package", packageName)
.addHeader("X-Android-Cert", sha1Cert)
.build()
return chain.proceed(newRequest)
}

companion object {
private fun computeSha1(context: Context): String = try {
val signatures = context.packageManager
.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
.signingInfo?.apkContentsSigners
MessageDigest.getInstance("SHA-1")
.digest(signatures?.getOrNull(0)?.toByteArray())
.joinToString("") { "%02x".format(it) }
} catch (_: Exception) {
""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yapp.ndgl.data.core.interceptor

import okhttp3.Interceptor
import okhttp3.Response

class ApiKeyQueryInterceptor(private val apiKey: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val newUrl = chain.request().url.newBuilder()
.addQueryParameter("key", apiKey)
.build()
return chain.proceed(chain.request().newBuilder().url(newUrl).build())
}
}
2 changes: 2 additions & 0 deletions data/travel/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ android {
}
buildConfigField("String", "PLACE_API_KEY", "\"${localProperties.getProperty("PLACE_API_KEY", "")}\"")
buildConfigField("String", "ROUTE_API_KEY", "\"${localProperties.getProperty("ROUTE_API_KEY", "")}\"")
buildConfigField("String", "WEATHER_API_KEY", "\"${localProperties.getProperty("WEATHER_API_KEY", "")}\"")
buildConfigField("String", "EXCHANGE_RATE_API_KEY", "\"${localProperties.getProperty("EXCHANGE_RATE_API_KEY", "")}\"")
}

buildFeatures {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yapp.ndgl.data.travel.api

import com.yapp.ndgl.data.travel.model.ExchangeRateResponse
import retrofit2.http.GET
import retrofit2.http.Path

interface ExchangeRateApi {
@GET("v6/{apiKey}/latest/{base}")
suspend fun getLatestRate(
@Path("apiKey") apiKey: String,
@Path("base") base: String,
): ExchangeRateResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.yapp.ndgl.data.travel.api

import com.yapp.ndgl.data.travel.model.GeocodingResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface GeocodingApi {
@GET("maps/api/geocode/json")
suspend fun geocode(
@Query("address") address: String,
): GeocodingResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yapp.ndgl.data.travel.api

import com.yapp.ndgl.data.travel.model.WeatherForecastResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface WeatherApi {
@GET("v1/forecast/days:lookup")
suspend fun getDailyForecast(
@Query("location.latitude") latitude: Double,
@Query("location.longitude") longitude: Double,
@Query("days") days: Int,
): WeatherForecastResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import android.content.Context
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.net.PlacesClient
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.yapp.ndgl.data.core.di.ExchangeRateApiKey
import com.yapp.ndgl.data.core.di.ExchangeRateClient
import com.yapp.ndgl.data.core.di.GeocodingClient
import com.yapp.ndgl.data.core.di.RouteApiKey
import com.yapp.ndgl.data.core.di.RouteBaseUrl
import com.yapp.ndgl.data.core.di.RouteClient
import com.yapp.ndgl.data.core.di.WeatherApiKey
import com.yapp.ndgl.data.core.di.WeatherClient
import com.yapp.ndgl.data.travel.BuildConfig
import com.yapp.ndgl.data.travel.api.ExchangeRateApi
import com.yapp.ndgl.data.travel.api.GeocodingApi
import com.yapp.ndgl.data.travel.api.PlaceApi
import com.yapp.ndgl.data.travel.api.RouteApi
import com.yapp.ndgl.data.travel.api.TravelProgramApi
import com.yapp.ndgl.data.travel.api.TravelTemplateApi
import com.yapp.ndgl.data.travel.api.UserTravelApi
import com.yapp.ndgl.data.travel.api.WeatherApi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -28,6 +36,9 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object TravelNetworkModule {
private const val ROUTES_BASE_URL = "https://routes.googleapis.com/"
private const val WEATHER_BASE_URL = "https://weather.googleapis.com/"
private const val GEOCODING_BASE_URL = "https://maps.googleapis.com/"
private const val EXCHANGE_RATE_BASE_URL = "https://v6.exchangerate-api.com/"

@Provides
@Singleton
Expand All @@ -45,6 +56,45 @@ object TravelNetworkModule {
@Singleton
fun provideRouteApiKey(): String = BuildConfig.ROUTE_API_KEY

@WeatherApiKey
@Provides
@Singleton
fun provideWeatherApiKey(): String = BuildConfig.WEATHER_API_KEY

@WeatherClient
@Provides
@Singleton
fun provideWeatherRetrofit(
@WeatherClient okHttpClient: OkHttpClient,
json: Json,
): Retrofit = Retrofit.Builder()
.baseUrl(WEATHER_BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()

@Provides
@Singleton
fun provideWeatherApi(@WeatherClient retrofit: Retrofit): WeatherApi =
retrofit.create(WeatherApi::class.java)

@GeocodingClient
@Provides
@Singleton
fun provideGeocodingRetrofit(
@WeatherClient okHttpClient: OkHttpClient,
json: Json,
): Retrofit = Retrofit.Builder()
.baseUrl(GEOCODING_BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()

@Provides
@Singleton
fun provideGeocodingApi(@GeocodingClient retrofit: Retrofit): GeocodingApi =
retrofit.create(GeocodingApi::class.java)

@RouteBaseUrl
@Provides
@Singleton
Expand Down Expand Up @@ -94,4 +144,27 @@ object TravelNetworkModule {
fun provideRouteApi(
@RouteClient retrofit: Retrofit,
): RouteApi = retrofit.create(RouteApi::class.java)

@ExchangeRateApiKey
@Provides
@Singleton
fun provideExchangeRateApiKey(): String = BuildConfig.EXCHANGE_RATE_API_KEY

@ExchangeRateClient
@Provides
@Singleton
fun provideExchangeRateRetrofit(
@ExchangeRateClient okHttpClient: OkHttpClient,
json: Json,
): Retrofit = Retrofit.Builder()
.baseUrl(EXCHANGE_RATE_BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()

@Provides
@Singleton
fun provideExchangeRateApi(
@ExchangeRateClient retrofit: Retrofit,
): ExchangeRateApi = retrofit.create(ExchangeRateApi::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.yapp.ndgl.data.travel.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ExchangeRateResponse(
@SerialName("result") val result: String,
@SerialName("base_code") val baseCode: String,
@SerialName("time_last_update_utc") val timeLastUpdateUtc: String,
@SerialName("conversion_rates") val conversionRates: Map<String, Double>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.yapp.ndgl.data.travel.model

import kotlinx.serialization.Serializable

@Serializable
data class GeocodingResponse(
val results: List<Result> = emptyList(),
val status: String = "",
) {
@Serializable
data class Result(val geometry: Geometry)

@Serializable
data class Geometry(val location: Location)

@Serializable
data class Location(val lat: Double, val lng: Double)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class)

package com.yapp.ndgl.data.travel.model

import com.yapp.ndgl.data.core.serializer.LocalDateSerializer
Expand All @@ -16,5 +18,6 @@ data class UpcomingTravelResponse(
val endDate: LocalDate,
val nights: Int,
val days: Int,
val thumbnail: String?,
val upcomingUserTravelPlace: UserTravelPlace? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class)

package com.yapp.ndgl.data.travel.model

import kotlinx.serialization.Serializable

@Serializable
data class WeatherForecastResponse(
val forecastDays: List<ForecastDay> = emptyList(),
) {
@Serializable
data class ForecastDay(
val displayDate: DisplayDate,
val daytimeForecast: DaytimeForecast? = null,
val maxTemperature: Temperature? = null,
val minTemperature: Temperature? = null,
)

@Serializable
data class DisplayDate(val year: Int, val month: Int, val day: Int)

@Serializable
data class DaytimeForecast(val weatherCondition: WeatherCondition? = null)

@Serializable
data class WeatherCondition(
val type: String? = null,
val iconBaseUri: String? = null,
)

@Serializable
data class Temperature(val degrees: Double, val unit: String = "CELSIUS")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.yapp.ndgl.data.travel.repository

import com.yapp.ndgl.data.core.di.ExchangeRateApiKey
import com.yapp.ndgl.data.travel.api.ExchangeRateApi
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ExchangeRateRepository @Inject constructor(
private val exchangeRateApi: ExchangeRateApi,
@ExchangeRateApiKey private val apiKey: String,
) {
suspend fun getKrwRate(currencyCode: String): Double? {
return exchangeRateApi.getLatestRate(apiKey, currencyCode)
.conversionRates["KRW"]
}

suspend fun getSupportedCurrencyCodes(): List<String> {
return exchangeRateApi.getLatestRate(apiKey, "USD").conversionRates.keys.toList()
}
}
Loading