Skip to content

Commit fa40640

Browse files
authored
Merge pull request #38 from YAPP-Github/feature/NDGL-104/impl-travel-helper
[NDGL-104] 여행 도구 탭 구현
2 parents 38a56ad + 502c2ab commit fa40640

36 files changed

+2400
-93
lines changed

data/core/src/main/java/com/yapp/ndgl/data/core/di/NetworkModule.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact
44
import com.yapp.ndgl.data.core.BuildConfig
55
import com.yapp.ndgl.data.core.adapter.NDGLCallAdapterFactory
66
import com.yapp.ndgl.data.core.authenticator.NDGLAuthenticator
7+
import com.yapp.ndgl.data.core.interceptor.AndroidCredentialInterceptor
78
import com.yapp.ndgl.data.core.interceptor.ApiKeyInterceptor
9+
import com.yapp.ndgl.data.core.interceptor.ApiKeyQueryInterceptor
810
import com.yapp.ndgl.data.core.interceptor.NDGLInterceptor
911
import com.yapp.ndgl.data.core.interceptor.RouteInterceptor
1012
import dagger.Module
@@ -121,6 +123,28 @@ object NetworkModule {
121123
.addInterceptor(httpLoggingInterceptor)
122124
.build()
123125
}
126+
127+
@WeatherClient
128+
@Singleton
129+
@Provides
130+
fun provideWeatherOkHttpClient(
131+
@WeatherApiKey apiKey: String,
132+
androidCredentialInterceptor: AndroidCredentialInterceptor,
133+
httpLoggingInterceptor: HttpLoggingInterceptor,
134+
): OkHttpClient = OkHttpClient.Builder()
135+
.addInterceptor(ApiKeyQueryInterceptor(apiKey))
136+
.addInterceptor(androidCredentialInterceptor)
137+
.addInterceptor(httpLoggingInterceptor)
138+
.build()
139+
140+
@ExchangeRateClient
141+
@Singleton
142+
@Provides
143+
fun provideExchangeRateOkHttpClient(
144+
httpLoggingInterceptor: HttpLoggingInterceptor,
145+
): OkHttpClient = OkHttpClient.Builder()
146+
.addInterceptor(httpLoggingInterceptor)
147+
.build()
124148
}
125149

126150
@Qualifier
@@ -146,3 +170,23 @@ annotation class RouteBaseUrl
146170
@Qualifier
147171
@Retention(AnnotationRetention.BINARY)
148172
annotation class RouteClient
173+
174+
@Qualifier
175+
@Retention(AnnotationRetention.BINARY)
176+
annotation class WeatherApiKey
177+
178+
@Qualifier
179+
@Retention(AnnotationRetention.BINARY)
180+
annotation class WeatherClient
181+
182+
@Qualifier
183+
@Retention(AnnotationRetention.BINARY)
184+
annotation class GeocodingClient
185+
186+
@Qualifier
187+
@Retention(AnnotationRetention.BINARY)
188+
annotation class ExchangeRateApiKey
189+
190+
@Qualifier
191+
@Retention(AnnotationRetention.BINARY)
192+
annotation class ExchangeRateClient
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.yapp.ndgl.data.core.interceptor
2+
3+
import android.content.Context
4+
import android.content.pm.PackageManager
5+
import dagger.hilt.android.qualifiers.ApplicationContext
6+
import okhttp3.Interceptor
7+
import okhttp3.Response
8+
import java.security.MessageDigest
9+
import javax.inject.Inject
10+
11+
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
12+
class AndroidCredentialInterceptor @Inject constructor(
13+
@ApplicationContext private val context: Context,
14+
) : Interceptor {
15+
private val packageName: String = context.packageName
16+
private val sha1Cert: String = computeSha1(context)
17+
18+
override fun intercept(chain: Interceptor.Chain): Response {
19+
val newRequest = chain.request().newBuilder()
20+
.addHeader("X-Android-Package", packageName)
21+
.addHeader("X-Android-Cert", sha1Cert)
22+
.build()
23+
return chain.proceed(newRequest)
24+
}
25+
26+
companion object {
27+
private fun computeSha1(context: Context): String = try {
28+
val signatures = context.packageManager
29+
.getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES)
30+
.signingInfo?.apkContentsSigners
31+
MessageDigest.getInstance("SHA-1")
32+
.digest(signatures?.getOrNull(0)?.toByteArray())
33+
.joinToString("") { "%02x".format(it) }
34+
} catch (_: Exception) {
35+
""
36+
}
37+
}
38+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.yapp.ndgl.data.core.interceptor
2+
3+
import okhttp3.Interceptor
4+
import okhttp3.Response
5+
6+
class ApiKeyQueryInterceptor(private val apiKey: String) : Interceptor {
7+
override fun intercept(chain: Interceptor.Chain): Response {
8+
val newUrl = chain.request().url.newBuilder()
9+
.addQueryParameter("key", apiKey)
10+
.build()
11+
return chain.proceed(chain.request().newBuilder().url(newUrl).build())
12+
}
13+
}

data/travel/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ android {
1313
}
1414
buildConfigField("String", "PLACE_API_KEY", "\"${localProperties.getProperty("PLACE_API_KEY", "")}\"")
1515
buildConfigField("String", "ROUTE_API_KEY", "\"${localProperties.getProperty("ROUTE_API_KEY", "")}\"")
16+
buildConfigField("String", "WEATHER_API_KEY", "\"${localProperties.getProperty("WEATHER_API_KEY", "")}\"")
17+
buildConfigField("String", "EXCHANGE_RATE_API_KEY", "\"${localProperties.getProperty("EXCHANGE_RATE_API_KEY", "")}\"")
1618
}
1719

1820
buildFeatures {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.yapp.ndgl.data.travel.api
2+
3+
import com.yapp.ndgl.data.travel.model.ExchangeRateResponse
4+
import retrofit2.http.GET
5+
import retrofit2.http.Path
6+
7+
interface ExchangeRateApi {
8+
@GET("v6/{apiKey}/latest/{base}")
9+
suspend fun getLatestRate(
10+
@Path("apiKey") apiKey: String,
11+
@Path("base") base: String,
12+
): ExchangeRateResponse
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.yapp.ndgl.data.travel.api
2+
3+
import com.yapp.ndgl.data.travel.model.GeocodingResponse
4+
import retrofit2.http.GET
5+
import retrofit2.http.Query
6+
7+
interface GeocodingApi {
8+
@GET("maps/api/geocode/json")
9+
suspend fun geocode(
10+
@Query("address") address: String,
11+
): GeocodingResponse
12+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.yapp.ndgl.data.travel.api
2+
3+
import com.yapp.ndgl.data.travel.model.WeatherForecastResponse
4+
import retrofit2.http.GET
5+
import retrofit2.http.Query
6+
7+
interface WeatherApi {
8+
@GET("v1/forecast/days:lookup")
9+
suspend fun getDailyForecast(
10+
@Query("location.latitude") latitude: Double,
11+
@Query("location.longitude") longitude: Double,
12+
@Query("days") days: Int,
13+
): WeatherForecastResponse
14+
}

data/travel/src/main/java/com/yapp/ndgl/data/travel/di/TravelNetworkModule.kt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@ import android.content.Context
44
import com.google.android.libraries.places.api.Places
55
import com.google.android.libraries.places.api.net.PlacesClient
66
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
7+
import com.yapp.ndgl.data.core.di.ExchangeRateApiKey
8+
import com.yapp.ndgl.data.core.di.ExchangeRateClient
9+
import com.yapp.ndgl.data.core.di.GeocodingClient
710
import com.yapp.ndgl.data.core.di.RouteApiKey
811
import com.yapp.ndgl.data.core.di.RouteBaseUrl
912
import com.yapp.ndgl.data.core.di.RouteClient
13+
import com.yapp.ndgl.data.core.di.WeatherApiKey
14+
import com.yapp.ndgl.data.core.di.WeatherClient
1015
import com.yapp.ndgl.data.travel.BuildConfig
16+
import com.yapp.ndgl.data.travel.api.ExchangeRateApi
17+
import com.yapp.ndgl.data.travel.api.GeocodingApi
1118
import com.yapp.ndgl.data.travel.api.PlaceApi
1219
import com.yapp.ndgl.data.travel.api.RouteApi
1320
import com.yapp.ndgl.data.travel.api.TravelProgramApi
1421
import com.yapp.ndgl.data.travel.api.TravelTemplateApi
1522
import com.yapp.ndgl.data.travel.api.UserTravelApi
23+
import com.yapp.ndgl.data.travel.api.WeatherApi
1624
import dagger.Module
1725
import dagger.Provides
1826
import dagger.hilt.InstallIn
@@ -28,6 +36,9 @@ import javax.inject.Singleton
2836
@InstallIn(SingletonComponent::class)
2937
object TravelNetworkModule {
3038
private const val ROUTES_BASE_URL = "https://routes.googleapis.com/"
39+
private const val WEATHER_BASE_URL = "https://weather.googleapis.com/"
40+
private const val GEOCODING_BASE_URL = "https://maps.googleapis.com/"
41+
private const val EXCHANGE_RATE_BASE_URL = "https://v6.exchangerate-api.com/"
3142

3243
@Provides
3344
@Singleton
@@ -45,6 +56,45 @@ object TravelNetworkModule {
4556
@Singleton
4657
fun provideRouteApiKey(): String = BuildConfig.ROUTE_API_KEY
4758

59+
@WeatherApiKey
60+
@Provides
61+
@Singleton
62+
fun provideWeatherApiKey(): String = BuildConfig.WEATHER_API_KEY
63+
64+
@WeatherClient
65+
@Provides
66+
@Singleton
67+
fun provideWeatherRetrofit(
68+
@WeatherClient okHttpClient: OkHttpClient,
69+
json: Json,
70+
): Retrofit = Retrofit.Builder()
71+
.baseUrl(WEATHER_BASE_URL)
72+
.client(okHttpClient)
73+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
74+
.build()
75+
76+
@Provides
77+
@Singleton
78+
fun provideWeatherApi(@WeatherClient retrofit: Retrofit): WeatherApi =
79+
retrofit.create(WeatherApi::class.java)
80+
81+
@GeocodingClient
82+
@Provides
83+
@Singleton
84+
fun provideGeocodingRetrofit(
85+
@WeatherClient okHttpClient: OkHttpClient,
86+
json: Json,
87+
): Retrofit = Retrofit.Builder()
88+
.baseUrl(GEOCODING_BASE_URL)
89+
.client(okHttpClient)
90+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
91+
.build()
92+
93+
@Provides
94+
@Singleton
95+
fun provideGeocodingApi(@GeocodingClient retrofit: Retrofit): GeocodingApi =
96+
retrofit.create(GeocodingApi::class.java)
97+
4898
@RouteBaseUrl
4999
@Provides
50100
@Singleton
@@ -94,4 +144,27 @@ object TravelNetworkModule {
94144
fun provideRouteApi(
95145
@RouteClient retrofit: Retrofit,
96146
): RouteApi = retrofit.create(RouteApi::class.java)
147+
148+
@ExchangeRateApiKey
149+
@Provides
150+
@Singleton
151+
fun provideExchangeRateApiKey(): String = BuildConfig.EXCHANGE_RATE_API_KEY
152+
153+
@ExchangeRateClient
154+
@Provides
155+
@Singleton
156+
fun provideExchangeRateRetrofit(
157+
@ExchangeRateClient okHttpClient: OkHttpClient,
158+
json: Json,
159+
): Retrofit = Retrofit.Builder()
160+
.baseUrl(EXCHANGE_RATE_BASE_URL)
161+
.client(okHttpClient)
162+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
163+
.build()
164+
165+
@Provides
166+
@Singleton
167+
fun provideExchangeRateApi(
168+
@ExchangeRateClient retrofit: Retrofit,
169+
): ExchangeRateApi = retrofit.create(ExchangeRateApi::class.java)
97170
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class ExchangeRateResponse(
8+
@SerialName("result") val result: String,
9+
@SerialName("base_code") val baseCode: String,
10+
@SerialName("time_last_update_utc") val timeLastUpdateUtc: String,
11+
@SerialName("conversion_rates") val conversionRates: Map<String, Double>,
12+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.yapp.ndgl.data.travel.model
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class GeocodingResponse(
7+
val results: List<Result> = emptyList(),
8+
val status: String = "",
9+
) {
10+
@Serializable
11+
data class Result(val geometry: Geometry)
12+
13+
@Serializable
14+
data class Geometry(val location: Location)
15+
16+
@Serializable
17+
data class Location(val lat: Double, val lng: Double)
18+
}

0 commit comments

Comments
 (0)