Skip to content

Commit 3f5f7ec

Browse files
committed
[NDGL-104] feat: 여행 도구 - 여행 날씨 정보 조회
1 parent 38a56ad commit 3f5f7ec

22 files changed

Lines changed: 1597 additions & 48 deletions

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

Lines changed: 27 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,19 @@ 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()
124139
}
125140

126141
@Qualifier
@@ -146,3 +161,15 @@ annotation class RouteBaseUrl
146161
@Qualifier
147162
@Retention(AnnotationRetention.BINARY)
148163
annotation class RouteClient
164+
165+
@Qualifier
166+
@Retention(AnnotationRetention.BINARY)
167+
annotation class WeatherApiKey
168+
169+
@Qualifier
170+
@Retention(AnnotationRetention.BINARY)
171+
annotation class WeatherClient
172+
173+
@Qualifier
174+
@Retention(AnnotationRetention.BINARY)
175+
annotation class GeocodingClient
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 (e: Exception) { "" }
35+
}
36+
}
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ 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", "")}\"")
1617
}
1718

1819
buildFeatures {
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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ 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.GeocodingClient
78
import com.yapp.ndgl.data.core.di.RouteApiKey
89
import com.yapp.ndgl.data.core.di.RouteBaseUrl
910
import com.yapp.ndgl.data.core.di.RouteClient
11+
import com.yapp.ndgl.data.core.di.WeatherApiKey
12+
import com.yapp.ndgl.data.core.di.WeatherClient
1013
import com.yapp.ndgl.data.travel.BuildConfig
14+
import com.yapp.ndgl.data.travel.api.GeocodingApi
1115
import com.yapp.ndgl.data.travel.api.PlaceApi
1216
import com.yapp.ndgl.data.travel.api.RouteApi
1317
import com.yapp.ndgl.data.travel.api.TravelProgramApi
1418
import com.yapp.ndgl.data.travel.api.TravelTemplateApi
1519
import com.yapp.ndgl.data.travel.api.UserTravelApi
20+
import com.yapp.ndgl.data.travel.api.WeatherApi
1621
import dagger.Module
1722
import dagger.Provides
1823
import dagger.hilt.InstallIn
@@ -28,6 +33,8 @@ import javax.inject.Singleton
2833
@InstallIn(SingletonComponent::class)
2934
object TravelNetworkModule {
3035
private const val ROUTES_BASE_URL = "https://routes.googleapis.com/"
36+
private const val WEATHER_BASE_URL = "https://weather.googleapis.com/"
37+
private const val GEOCODING_BASE_URL = "https://maps.googleapis.com/"
3138

3239
@Provides
3340
@Singleton
@@ -45,6 +52,45 @@ object TravelNetworkModule {
4552
@Singleton
4653
fun provideRouteApiKey(): String = BuildConfig.ROUTE_API_KEY
4754

55+
@WeatherApiKey
56+
@Provides
57+
@Singleton
58+
fun provideWeatherApiKey(): String = BuildConfig.WEATHER_API_KEY
59+
60+
@WeatherClient
61+
@Provides
62+
@Singleton
63+
fun provideWeatherRetrofit(
64+
@WeatherClient okHttpClient: OkHttpClient,
65+
json: Json,
66+
): Retrofit = Retrofit.Builder()
67+
.baseUrl(WEATHER_BASE_URL)
68+
.client(okHttpClient)
69+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
70+
.build()
71+
72+
@Provides
73+
@Singleton
74+
fun provideWeatherApi(@WeatherClient retrofit: Retrofit): WeatherApi =
75+
retrofit.create(WeatherApi::class.java)
76+
77+
@GeocodingClient
78+
@Provides
79+
@Singleton
80+
fun provideGeocodingRetrofit(
81+
@WeatherClient okHttpClient: OkHttpClient,
82+
json: Json,
83+
): Retrofit = Retrofit.Builder()
84+
.baseUrl(GEOCODING_BASE_URL)
85+
.client(okHttpClient)
86+
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
87+
.build()
88+
89+
@Provides
90+
@Singleton
91+
fun provideGeocodingApi(@GeocodingClient retrofit: Retrofit): GeocodingApi =
92+
retrofit.create(GeocodingApi::class.java)
93+
4894
@RouteBaseUrl
4995
@Provides
5096
@Singleton
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+
}

data/travel/src/main/java/com/yapp/ndgl/data/travel/model/UpcomingTravelResponse.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class)
2+
13
package com.yapp.ndgl.data.travel.model
24

35
import com.yapp.ndgl.data.core.serializer.LocalDateSerializer
@@ -16,5 +18,6 @@ data class UpcomingTravelResponse(
1618
val endDate: LocalDate,
1719
val nights: Int,
1820
val days: Int,
21+
val thumbnail: String?,
1922
val upcomingUserTravelPlace: UserTravelPlace? = null,
2023
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class)
2+
3+
package com.yapp.ndgl.data.travel.model
4+
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
data class WeatherForecastResponse(
9+
val forecastDays: List<ForecastDay> = emptyList(),
10+
) {
11+
@Serializable
12+
data class ForecastDay(
13+
val displayDate: DisplayDate,
14+
val daytimeForecast: DaytimeForecast? = null,
15+
val maxTemperature: Temperature? = null,
16+
val minTemperature: Temperature? = null,
17+
)
18+
19+
@Serializable
20+
data class DisplayDate(val year: Int, val month: Int, val day: Int)
21+
22+
@Serializable
23+
data class DaytimeForecast(val weatherCondition: WeatherCondition? = null)
24+
25+
@Serializable
26+
data class WeatherCondition(
27+
val type: String? = null,
28+
val iconBaseUri: String? = null,
29+
)
30+
31+
@Serializable
32+
data class Temperature(val degrees: Double, val unit: String = "CELSIUS")
33+
}

0 commit comments

Comments
 (0)