Skip to content

Commit ee8d822

Browse files
committed
add network for external camera with retrofit [FoKE-Developers#35]
- 임시로 `http` 사용 가능하도록 설정 - 일부 `data` 모듈에서 사용할 예정인 공용 코드는 `util`에 구현
1 parent 95bd2bb commit ee8d822

File tree

15 files changed

+392
-1
lines changed

15 files changed

+392
-1
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
android:name=".MainApplication"
1010
android:icon="@mipmap/ic_launcher"
1111
android:label="@string/app_name"
12-
android:roundIcon="@mipmap/ic_launcher_round" />
12+
android:roundIcon="@mipmap/ic_launcher_round"
13+
android:usesCleartextTraffic="true" />
14+
<!-- TODO: temporary add usesCleartextTraffic. remove later -->
1315

1416
<!-- TODO: support back-up-->
1517
<!-- android:allowBackup="true"-->
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.foke.together.domain.output
2+
3+
import android.graphics.Bitmap
4+
5+
interface ExternalCameraRepositoryInterface {
6+
fun setCameraSourceUrl(url: String)
7+
suspend fun capture(): Result<Bitmap>
8+
fun getPreviewUrl(): String
9+
}

external/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ dependencies {
1515
androidTestImplementation(libs.androidx.junit)
1616
androidTestImplementation(libs.androidx.espresso.core)
1717

18+
// retrofit
19+
implementation(libs.ok.http.base)
20+
implementation(libs.retrofit.base)
21+
implementation(libs.retrofit.gson)
22+
implementation(libs.google.gson)
23+
1824
// module dependency
1925
implementation(project(":domain"))
2026

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.foke.together.external.network
2+
3+
import okhttp3.ResponseBody
4+
import retrofit2.http.GET
5+
import retrofit2.http.Query
6+
7+
interface ExternalCameraApi {
8+
@GET("/capture")
9+
suspend fun capture(
10+
@Query("timeout") timeout: Int? = null
11+
): Result<ResponseBody>
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.foke.together.external.network
2+
3+
import com.foke.together.external.network.interceptor.BaseUrlInterceptor
4+
import okhttp3.ResponseBody
5+
import javax.inject.Inject
6+
7+
class ExternalCameraDataSource @Inject constructor(
8+
private val externalCameraApi: ExternalCameraApi,
9+
private val baseUrlInterceptor: BaseUrlInterceptor
10+
){
11+
fun setBaseUrl(url: String) {
12+
baseUrlInterceptor.setBaseUrl(url)
13+
}
14+
15+
suspend fun capture(timeout: Int? = null): Result<ResponseBody> {
16+
return externalCameraApi.capture(timeout)
17+
}
18+
19+
fun getPreviewUrl(): String =
20+
"${baseUrlInterceptor.getBaseUrl()}/preview"
21+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.foke.together.external.network
2+
3+
import com.foke.together.external.network.interceptor.BaseUrlInterceptor
4+
import com.foke.together.util.AppPolicy
5+
import com.foke.together.util.retrofit.NetworkCallAdapterFactory
6+
import com.google.gson.GsonBuilder
7+
import dagger.Module
8+
import dagger.Provides
9+
import dagger.hilt.InstallIn
10+
import dagger.hilt.components.SingletonComponent
11+
import okhttp3.OkHttpClient
12+
import retrofit2.Retrofit
13+
import retrofit2.converter.gson.GsonConverterFactory
14+
import java.util.concurrent.TimeUnit
15+
import javax.inject.Named
16+
import javax.inject.Singleton
17+
18+
@Module
19+
@InstallIn(SingletonComponent::class)
20+
object ExternalCameraModule {
21+
22+
@Provides
23+
@Named("cameraIPUrl")
24+
// TODO: need to check in phase3; is it valid injection?
25+
fun provideCameraIPUrl(): String = AppPolicy.EXTERNAL_CAMERA_DEFAULT_SERVER_URL
26+
27+
@Provides
28+
@Singleton
29+
fun provideBaseUrlInterceptor() = BaseUrlInterceptor()
30+
31+
@Singleton
32+
@Provides
33+
fun provideOkHttpClient(
34+
baseUrlInterceptor: BaseUrlInterceptor
35+
) = OkHttpClient.Builder()
36+
.connectTimeout(AppPolicy.EXTERNAL_CAMERA_CONNECT_TIMEOUT, TimeUnit.SECONDS)
37+
.readTimeout(AppPolicy.EXTERNAL_CAMERA_READ_TIMEOUT, TimeUnit.SECONDS)
38+
.writeTimeout(AppPolicy.EXTERNAL_CAMERA_WRITE_TIMEOUT, TimeUnit.SECONDS)
39+
// .addInterceptor(MockInterceptor()) // TODO: add mock code for test
40+
.addInterceptor(baseUrlInterceptor)
41+
.build()
42+
43+
@Singleton
44+
@Provides
45+
fun provideExternalCameraServerRetrofit(
46+
okHttpClient: OkHttpClient,
47+
@Named("cameraIPUrl") cameraIPUrl: String
48+
) = Retrofit.Builder()
49+
.baseUrl(cameraIPUrl)
50+
.addConverterFactory(GsonConverterFactory.create(
51+
GsonBuilder().setLenient().create()
52+
))
53+
.addCallAdapterFactory(NetworkCallAdapterFactory())
54+
.client(okHttpClient)
55+
.build()
56+
57+
@Singleton
58+
@Provides
59+
fun provideApiService(retrofit: Retrofit): ExternalCameraApi =
60+
retrofit.create(ExternalCameraApi::class.java)
61+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.foke.together.external.network.interceptor
2+
3+
import com.foke.together.util.AppLog
4+
import com.foke.together.util.AppPolicy
5+
import okhttp3.Interceptor
6+
import okhttp3.Response
7+
8+
class BaseUrlInterceptor: Interceptor {
9+
@Volatile
10+
private var baseUrl: String? = null
11+
@Volatile
12+
private var scheme: String? = null
13+
@Volatile
14+
private var host: String? = null
15+
@Volatile
16+
private var port: Int? = null
17+
18+
fun getBaseUrl() = baseUrl ?: AppPolicy.EXTERNAL_CAMERA_DEFAULT_SERVER_URL
19+
20+
fun setBaseUrl(newBaseUrl: String) {
21+
baseUrl = newBaseUrl
22+
try {
23+
val sep = newBaseUrl.indexOf("://")
24+
scheme = newBaseUrl.substring(0, sep)
25+
val newHost = newBaseUrl.substring(sep + 3, newBaseUrl.length)
26+
27+
val pSep = newHost.lastIndexOf(":")
28+
port = if (pSep != -1) {
29+
host = newHost.substring(0, pSep)
30+
newHost.substring(pSep + 1, newHost.length).toInt()
31+
} else {
32+
host = newHost
33+
null
34+
}
35+
} catch (e: Exception) {
36+
AppLog.e(TAG, "setBaseUrl", "cannot parse newBaseUrl")
37+
}
38+
}
39+
40+
override fun intercept(chain: Interceptor.Chain): Response {
41+
var request = chain.request()
42+
scheme?.let { s ->
43+
host?.let { h ->
44+
val newUrl = request.url.newBuilder().run {
45+
scheme(s)
46+
host(h)
47+
port?.let { port(it) }
48+
build()
49+
}
50+
request = request.newBuilder()
51+
.url(newUrl)
52+
.build()
53+
}
54+
}
55+
return chain.proceed(request)
56+
}
57+
58+
companion object {
59+
private val TAG: String = BaseUrlInterceptor::class.java.simpleName
60+
}
61+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.foke.together.external.network.interceptor
2+
3+
import okhttp3.Interceptor
4+
import okhttp3.Protocol
5+
import okhttp3.Response
6+
7+
class MockInterceptor: Interceptor {
8+
override fun intercept(chain: Interceptor.Chain): Response {
9+
// TODO: make mock for test
10+
return Response.Builder()
11+
.request(chain.request())
12+
.protocol(Protocol.HTTP_2)
13+
.build()
14+
}
15+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.foke.together.external.repository
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
5+
import com.foke.together.domain.output.ExternalCameraRepositoryInterface
6+
import com.foke.together.external.network.ExternalCameraDataSource
7+
import javax.inject.Inject
8+
9+
class ExternalCameraRepository @Inject constructor(
10+
private val externalCameraDataSource: ExternalCameraDataSource
11+
): ExternalCameraRepositoryInterface {
12+
override fun setCameraSourceUrl(url: String) {
13+
externalCameraDataSource.setBaseUrl(url)
14+
}
15+
16+
override suspend fun capture(): Result<Bitmap> {
17+
return externalCameraDataSource.capture()
18+
.map { responseBody ->
19+
BitmapFactory.decodeStream(responseBody.byteStream())
20+
}
21+
}
22+
23+
override fun getPreviewUrl() =
24+
externalCameraDataSource.getPreviewUrl()
25+
26+
companion object {
27+
private val TAG = ExternalCameraRepository::class.java.simpleName
28+
}
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.foke.together.external.repository.di
2+
3+
import com.foke.together.domain.output.ExternalCameraRepositoryInterface
4+
import com.foke.together.external.repository.ExternalCameraRepository
5+
import dagger.Binds
6+
import dagger.Module
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.android.components.ViewModelComponent
9+
10+
@Module
11+
@InstallIn(ViewModelComponent::class)
12+
abstract class RepositoryModule {
13+
@Binds
14+
abstract fun bindAppPreferenceRepository(
15+
externalCameraRepository: ExternalCameraRepository
16+
): ExternalCameraRepositoryInterface
17+
}

0 commit comments

Comments
 (0)