Skip to content

Commit b8e3e68

Browse files
authored
Feature/profile image (#47)
* 프로필 이미지 업로드 UI, UseCase구현 * image upload api 구현(미완) * file provider 추가 * image upload api 수정 * 이미지 저장되지 않는 현상 수정
1 parent 298591a commit b8e3e68

File tree

22 files changed

+427
-51
lines changed

22 files changed

+427
-51
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
android:value="androidx.startup" />
2727
</provider>
2828

29+
<provider
30+
android:name="androidx.core.content.FileProvider"
31+
android:authorities="com.dkin.chevit.fileprovider"
32+
android:grantUriPermissions="true"
33+
android:exported="false">
34+
<meta-data
35+
android:name="android.support.FILE_PROVIDER_PATHS"
36+
android:resource="@xml/file_paths" />
37+
</provider>
38+
2939
<activity
3040
android:name=".MainActivity"
3141
android:exported="true"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<paths xmlns:android="http://schemas.android.com/apk/res/android">
3+
<!--Context.getFilesDir()-->
4+
<files-path name="files" path="." />
5+
6+
<!--getCacheDir()-->
7+
<cache-path name="cache" path="." />
8+
9+
<!--Environment.getExternalStorageDirectory()-->
10+
<external-path name="external" path="." />
11+
12+
<!--Context.getExternalFilesDir(null)-->
13+
<external-files-path name="external_files" path="."/>
14+
15+
<!--Context.getExternalCacheDir()-->
16+
<external-cache-path name="external_cache" path="."/>
17+
18+
<!--only available on API 21+ devices.-->
19+
<!--Context.getExternalMediaDirs()-->
20+
<external-media-path name="external_media" path="."/>
21+
</paths>

data/src/debug/java/com/dkin/chevit/data/di/NetworkModule.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import okhttp3.OkHttpClient
2323
import okhttp3.logging.HttpLoggingInterceptor
2424
import retrofit2.Converter
2525
import retrofit2.Retrofit
26+
import javax.inject.Named
2627

2728
@Module
2829
@InstallIn(SingletonComponent::class)
@@ -86,6 +87,20 @@ internal object NetworkModule {
8687
.addInterceptor(tokenInterceptor)
8788
.build()
8889

90+
@Provides
91+
@Singleton
92+
@Named("Pure")
93+
fun providePureOkHttpClient(
94+
httpLoggingInterceptor: HttpLoggingInterceptor,
95+
chuckerInterceptor: ChuckerInterceptor,
96+
) = OkHttpClient.Builder()
97+
.connectTimeout(20, TimeUnit.SECONDS)
98+
.readTimeout(20, TimeUnit.SECONDS)
99+
.writeTimeout(20, TimeUnit.SECONDS)
100+
.addInterceptor(httpLoggingInterceptor)
101+
.addInterceptor(chuckerInterceptor)
102+
.build()
103+
89104
@Provides
90105
@Singleton
91106
fun provideRetrofit(
@@ -96,4 +111,16 @@ internal object NetworkModule {
96111
.addConverterFactory(jsonConverter)
97112
.baseUrl(BuildConfig.API_URL)
98113
.build()
114+
115+
@Provides
116+
@Singleton
117+
@Named("Pure")
118+
fun providePureRetrofit(
119+
@Named("Pure") okHttpClient: OkHttpClient,
120+
@JsonConverter jsonConverter: Converter.Factory,
121+
): Retrofit = Retrofit.Builder()
122+
.client(okHttpClient)
123+
.addConverterFactory(jsonConverter)
124+
.baseUrl(BuildConfig.API_URL)
125+
.build()
99126
}

data/src/main/java/com/dkin/chevit/data/di/RetrofitModule.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.dkin.chevit.data.di
22

33
import com.dkin.chevit.data.remote.AuthAPI
4+
import com.dkin.chevit.data.remote.ImageAPI
45
import com.dkin.chevit.data.remote.NotificationAPI
56
import com.dkin.chevit.data.remote.PlanAPI
67
import com.dkin.chevit.data.remote.ServiceAPI
@@ -10,6 +11,7 @@ import dagger.hilt.InstallIn
1011
import dagger.hilt.components.SingletonComponent
1112
import javax.inject.Singleton
1213
import retrofit2.Retrofit
14+
import javax.inject.Named
1315

1416
@Module
1517
@InstallIn(SingletonComponent::class)
@@ -37,4 +39,10 @@ internal object RetrofitModule {
3739
fun providePlanAPI(
3840
retrofit: Retrofit
3941
): PlanAPI = retrofit.create(PlanAPI::class.java)
42+
43+
@Provides
44+
@Singleton
45+
fun provideImageAPI(
46+
@Named("Pure") retrofit: Retrofit
47+
): ImageAPI = retrofit.create(ImageAPI::class.java)
4048
}

data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package com.dkin.chevit.data.di.usecase
22

33
import com.dkin.chevit.domain.base.CoroutineDispatcherProvider
44
import com.dkin.chevit.domain.repository.AuthRepository
5+
import com.dkin.chevit.domain.usecase.auth.GetProfileImageDataUseCase
56
import com.dkin.chevit.domain.usecase.auth.GetUserStateUseCase
67
import com.dkin.chevit.domain.usecase.auth.GetUserUseCase
78
import com.dkin.chevit.domain.usecase.auth.SignOutUseCase
89
import com.dkin.chevit.domain.usecase.auth.SignUpUserUseCase
910
import com.dkin.chevit.domain.usecase.auth.UpdateUserUseCase
11+
import com.dkin.chevit.domain.usecase.auth.UploadProfileImageUseCase
1012
import com.dkin.chevit.domain.usecase.auth.WithDrawUserUseCase
1113
import dagger.Module
1214
import dagger.Provides
@@ -69,4 +71,22 @@ internal object AuthUseCaseModule {
6971
coroutineDispatcherProvider,
7072
authRepository
7173
)
74+
75+
@Provides
76+
fun provideGetProfileImageDataUseCase(
77+
coroutineDispatcherProvider: CoroutineDispatcherProvider,
78+
authRepository: AuthRepository,
79+
) = GetProfileImageDataUseCase(
80+
coroutineDispatcherProvider,
81+
authRepository
82+
)
83+
84+
@Provides
85+
fun provideUploadProfileImageUseCase(
86+
coroutineDispatcherProvider: CoroutineDispatcherProvider,
87+
authRepository: AuthRepository,
88+
) = UploadProfileImageUseCase(
89+
coroutineDispatcherProvider,
90+
authRepository
91+
)
7292
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.dkin.chevit.data.model.request
2+
3+
import com.dkin.chevit.data.DataModel
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
internal data class ProfileImageUploadPayload(
9+
@SerialName("fileSize") val fileSize: Int,
10+
@SerialName("mimeType") val mimeType: String,
11+
) : DataModel
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dkin.chevit.data.model.response
2+
3+
import com.dkin.chevit.data.DataModel
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
@Serializable
8+
internal data class ProfileImageUploadResponse(
9+
@SerialName("uploadMethod") val uploadMethod: String = "",
10+
@SerialName("uploadURL") val uploadURL: String = "",
11+
@SerialName("imageURL") val imageURL: String = "",
12+
) : DataModel

data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.dkin.chevit.data.remote
22

3+
import com.dkin.chevit.data.model.request.ProfileImageUploadPayload
34
import com.dkin.chevit.data.model.request.SignUpPayload
45
import com.dkin.chevit.data.model.request.UpdateUserPayload
56
import com.dkin.chevit.data.model.request.ValidationNicknamePayload
7+
import com.dkin.chevit.data.model.response.ProfileImageUploadResponse
68
import com.dkin.chevit.data.model.response.UserResponse
9+
import com.dkin.chevit.domain.base.None
10+
import okhttp3.MultipartBody
11+
import okhttp3.RequestBody
712
import retrofit2.Response
813
import retrofit2.http.Body
914
import retrofit2.http.DELETE
1015
import retrofit2.http.GET
16+
import retrofit2.http.Multipart
1117
import retrofit2.http.POST
1218
import retrofit2.http.PUT
19+
import retrofit2.http.Part
20+
import retrofit2.http.Url
1321

1422
/**
1523
* 유저 정보 및 인증 관련 API 모음
@@ -29,4 +37,7 @@ internal interface AuthAPI {
2937

3038
@DELETE("deleteUser")
3139
suspend fun deleteUser(): Response<Unit>
40+
41+
@POST("getProfileUploadURL")
42+
suspend fun getProfileUploadURL(@Body body: ProfileImageUploadPayload): ProfileImageUploadResponse
3243
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.dkin.chevit.data.remote
2+
3+
import okhttp3.RequestBody
4+
import retrofit2.http.Body
5+
import retrofit2.http.PUT
6+
import retrofit2.http.Url
7+
8+
internal interface ImageAPI {
9+
@PUT
10+
suspend fun uploadProfileImage(
11+
@Url url: String,
12+
@Body file: RequestBody
13+
)
14+
}

data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
package com.dkin.chevit.data.repository
22

3+
import com.dkin.chevit.data.model.request.ProfileImageUploadPayload
34
import com.dkin.chevit.data.model.request.SignUpPayload
45
import com.dkin.chevit.data.model.request.UpdateUserPayload
56
import com.dkin.chevit.data.model.response.toUser
67
import com.dkin.chevit.data.remote.AuthAPI
8+
import com.dkin.chevit.data.remote.ImageAPI
9+
import com.dkin.chevit.domain.base.None
10+
import com.dkin.chevit.domain.model.ProfileImageData
711
import com.dkin.chevit.domain.model.UserState
812
import com.dkin.chevit.domain.repository.AuthRepository
913
import com.google.firebase.auth.FirebaseAuth
14+
import okhttp3.MediaType.Companion.toMediaTypeOrNull
15+
import okhttp3.MultipartBody
16+
import okhttp3.RequestBody
17+
import okhttp3.RequestBody.Companion.asRequestBody
18+
import java.io.File
1019
import javax.inject.Inject
1120

1221
internal class AuthRepositoryImpl @Inject constructor(
1322
private val authAPI: AuthAPI,
14-
private val auth: FirebaseAuth
23+
private val imageAPI: ImageAPI,
24+
private val auth: FirebaseAuth,
1525
) : AuthRepository {
1626
override suspend fun getUserState(): UserState {
1727
return runCatching {
@@ -43,4 +53,29 @@ internal class AuthRepositoryImpl @Inject constructor(
4353
auth.signOut()
4454
return getUserState()
4555
}
56+
57+
override suspend fun getProfileUploadURL(fileSize: Int): ProfileImageData {
58+
val result = authAPI.getProfileUploadURL(
59+
ProfileImageUploadPayload(
60+
fileSize = fileSize,
61+
mimeType = "image/jpeg"
62+
)
63+
)
64+
return ProfileImageData(
65+
uploadMethod = result.uploadMethod,
66+
uploadURL = result.uploadURL,
67+
uploadHeaders = "{\"Content-Type\": [\"image/jpeg\"]}",
68+
imageURL = result.imageURL,
69+
)
70+
}
71+
72+
override suspend fun uploadProfileImage(
73+
uploadURL: String,
74+
uploadMethod: String,
75+
uploadHeaders: String,
76+
file: File
77+
) {
78+
val requestFile: RequestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
79+
return imageAPI.uploadProfileImage(uploadURL, requestFile)
80+
}
4681
}

0 commit comments

Comments
 (0)