Skip to content

Commit

Permalink
Feat:[snsproject] 이미지 파일 업로드 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
Yoon-Chan committed Apr 25, 2024
1 parent e05b60f commit 11fb7b1
Show file tree
Hide file tree
Showing 22 changed files with 359 additions and 25 deletions.
28 changes: 28 additions & 0 deletions data/src/main/java/com/example/data/di/FileModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.data.di

import com.example.data.usecase.file.GetImageUseCaseImpl
import com.example.data.usecase.file.GetInputStreamUseCaseImpl
import com.example.data.usecase.file.UploadImageUseCaseImpl
import com.example.domain.usecase.file.GetImageUseCase
import com.example.domain.usecase.file.GetInputStreamUseCase
import com.example.domain.usecase.file.UploadImageUseCase
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent


@Module
@InstallIn(SingletonComponent::class)
abstract class FileModule {

@Binds
abstract fun bindGetInputStreamUseCase(getInputStreamUseCaseImpl: GetInputStreamUseCaseImpl) : GetInputStreamUseCase

@Binds
abstract fun bindGetImageUseCase(getImageUseCaseImpl: GetImageUseCaseImpl) : GetImageUseCase


@Binds
abstract fun bindUploadImageUseCase(uploadImageUseCaseImpl: UploadImageUseCaseImpl) : UploadImageUseCase
}
4 changes: 4 additions & 0 deletions data/src/main/java/com/example/data/di/RetrofitModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.example.data.di

import com.example.data.BuildConfig
import com.example.data.retrofit.AddInterceptor
import com.example.data.retrofit.FileService
import com.example.data.retrofit.UserService
import com.google.gson.GsonBuilder
import dagger.Module
Expand Down Expand Up @@ -41,4 +42,7 @@ class RetrofitModule {
@Provides
fun provideUserService(retrofit: Retrofit): UserService = retrofit.create(UserService::class.java)

@Provides
fun provideFileService(retrofit: Retrofit): FileService = retrofit.create(FileService::class.java)

}
12 changes: 9 additions & 3 deletions data/src/main/java/com/example/data/di/UserModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import com.example.data.usecase.LoginUseCaseImpl
import com.example.data.usecase.SetTokenUseCaseImpl
import com.example.data.usecase.SignUpUseCaseImpl
import com.example.data.usecase.main.setting.GetMyUserUseCaseImpl
import com.example.data.usecase.main.setting.UpdateMyNameUseCaseImpl
import com.example.data.usecase.main.setting.SetMyUserUseCaseImpl
import com.example.data.usecase.main.setting.SetProfileImageUseCaseImpl
import com.example.domain.usecase.login.ClearTokenUseCase
import com.example.domain.usecase.login.GetTokenUseCase
import com.example.domain.usecase.login.LoginUseCase
import com.example.domain.usecase.login.SetTokenUseCase
import com.example.domain.usecase.login.SignUpUseCase
import com.example.domain.usecase.main.setting.GetMyUserUseCase
import com.example.domain.usecase.main.setting.UpdateMyNameUseCase
import com.example.domain.usecase.main.setting.SetMyUserUseCase
import com.example.domain.usecase.main.setting.SetProfileImageUseCase
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -42,5 +44,9 @@ abstract class UserModule {
abstract fun bindGetMyUserUseCase(getMyUserUseCaseImpl: GetMyUserUseCaseImpl) : GetMyUserUseCase

@Binds
abstract fun bindGetMyNameInfoUserUseCase(getMyNameUseCaseImpl: UpdateMyNameUseCaseImpl) : UpdateMyNameUseCase
abstract fun bindGetMyNameInfoUserUseCase(getMyNameUseCaseImpl: SetMyUserUseCaseImpl) : SetMyUserUseCase

@Binds
abstract fun bindSetProfileImageUseCase(setProfileImageUseCaseImpl: SetProfileImageUseCaseImpl) : SetProfileImageUseCase

}
14 changes: 14 additions & 0 deletions data/src/main/java/com/example/data/model/FileDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.data.model

import com.google.gson.annotations.SerializedName

data class FileDto(
@SerializedName("id")
val id: Long,
@SerializedName("fileName")
val fileName: String,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("filePath")
val filePath:String
)
21 changes: 21 additions & 0 deletions data/src/main/java/com/example/data/retrofit/FileService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.data.retrofit

import com.example.data.model.CommonResponse
import com.example.data.model.FileDto
import okhttp3.MultipartBody
import retrofit2.http.Headers
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part

interface FileService {

@POST("files")
@Multipart
@Headers("ContentType: multipart/form-data;")
suspend fun uploadFile(
@Part fileName: MultipartBody.Part,
@Part file: MultipartBody.Part
): CommonResponse<FileDto>

}
36 changes: 36 additions & 0 deletions data/src/main/java/com/example/data/retrofit/UriRequestBody.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.data.retrofit

import android.net.Uri
import android.util.Log
import com.example.domain.usecase.file.GetInputStreamUseCase
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.FileNotFoundException
import okio.source

class UriRequestBody(
private val contentUri: String,
private val getInputStreamUseCase: GetInputStreamUseCase,
private val contentType: MediaType? = null,
private val contentLength: Long
) : RequestBody() {
override fun contentType(): MediaType? {
return contentType
}

override fun contentLength(): Long {
return contentLength
}

override fun writeTo(sink: BufferedSink) {
try{
getInputStreamUseCase(contentUri).getOrThrow()
.use { inputStream ->
sink.writeAll(inputStream.source())
}
}catch (e: FileNotFoundException) {
Log.e("UriRequestBody", "${e.message}")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.data.usecase.file

import android.content.Context
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import com.example.domain.model.Image
import com.example.domain.usecase.file.GetImageUseCase
import javax.inject.Inject

class GetImageUseCaseImpl @Inject constructor(
private val context: Context
) : GetImageUseCase {

override fun invoke(contentUri: String): Image? {
val uri = Uri.parse(contentUri)
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.MIME_TYPE,
)
val cursor = context.contentResolver.query(
uri,
projection,
null,
null,
null
)

return cursor?.use {
it.moveToNext()
val idIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val mimeIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)

val name = it.getString(nameIndex)
val size = it.getLong(sizeIndex)
val mimeType = it.getString(mimeIndex)
val image = Image(
uri = contentUri,
name = name,
size = size,
mimeType = mimeType
)

Log.e("GetImageUseCaseImpl curosr", "${image}")

image
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.data.usecase.file

import android.content.Context
import android.net.Uri
import com.example.domain.usecase.file.GetInputStreamUseCase
import java.io.InputStream
import javax.inject.Inject

class GetInputStreamUseCaseImpl @Inject constructor(
private val context: Context
) : GetInputStreamUseCase{

override fun invoke(contentUri: String): Result<InputStream> = runCatching {
val uri = Uri.parse(contentUri)
context.contentResolver.openInputStream(uri) ?: throw IllegalStateException("inputStream Error")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.data.usecase.file

import com.example.data.retrofit.FileService
import com.example.data.retrofit.UriRequestBody
import com.example.domain.model.Image
import com.example.domain.usecase.file.GetInputStreamUseCase
import com.example.domain.usecase.file.UploadImageUseCase
import javax.inject.Inject
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody

class UploadImageUseCaseImpl @Inject constructor(
private val fileService: FileService,
private val getInputStreamUseCase: GetInputStreamUseCase
): UploadImageUseCase {
override suspend fun invoke(image: Image): Result<String> = runCatching {
val fileNamePart = MultipartBody.Part.createFormData(
"fileName",
image.name
)

val requestBody = UriRequestBody(
image.uri,
getInputStreamUseCase,
image.mimeType.toMediaType(),
image.size
)

val filePart = MultipartBody.Part.createFormData(
"file",
image.name,
requestBody
)
fileService.uploadFile(
fileNamePart,
filePart
).data.filePath
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ package com.example.data.usecase.main.setting
import com.example.data.model.UpdateMyInfoParam
import com.example.data.retrofit.UserService
import com.example.domain.usecase.main.setting.GetMyUserUseCase
import com.example.domain.usecase.main.setting.UpdateMyNameUseCase
import com.example.domain.usecase.main.setting.SetMyUserUseCase
import javax.inject.Inject

class UpdateMyNameUseCaseImpl @Inject constructor(
class SetMyUserUseCaseImpl @Inject constructor(
private val service: UserService,
private val getMyUserUseCase: GetMyUserUseCase
) : UpdateMyNameUseCase {
override suspend fun invoke(userName: String): Result<Unit> = runCatching {
) : SetMyUserUseCase {
override suspend fun invoke(
userName: String?,
profileImageUrl: String?
): Result<Unit> = runCatching {
val user = getMyUserUseCase().getOrThrow()
val requestBody = UpdateMyInfoParam(
userName = userName,
extraUserInfo = user.profileImageUrl.orEmpty(),
profileFilePath = "",
userName = userName ?: user.username,
profileFilePath = profileImageUrl ?: user.profileImageUrl.orEmpty(),
extraUserInfo = "",

).toRequestBody()
service.patchMyPage(requestBody)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.data.usecase.main.setting

import com.example.data.BuildConfig
import com.example.domain.model.Image
import com.example.domain.usecase.file.GetImageUseCase
import com.example.domain.usecase.file.UploadImageUseCase
import com.example.domain.usecase.main.setting.GetMyUserUseCase
import com.example.domain.usecase.main.setting.SetMyUserUseCase
import com.example.domain.usecase.main.setting.SetProfileImageUseCase
import javax.inject.Inject

class SetProfileImageUseCaseImpl @Inject constructor(
private val uploadImageUseCase: UploadImageUseCase,
private val getImageUseCase: GetImageUseCase,
private val setMyUserUseCase: SetMyUserUseCase,
private val getMyUserUseCase: GetMyUserUseCase,
): SetProfileImageUseCase {
override suspend fun invoke(contentUri: String) : Result<Unit> = runCatching{
//0. 현재 user 정보 가져오기
val user = getMyUserUseCase().getOrThrow()

// 1. 이미지 가져오기
val image = getImageUseCase(contentUri) ?: throw NullPointerException("이미지를 찾을 수 없습니다.")

//2. 이미지 서버에 업로드하기
val imagePath = uploadImageUseCase(image).getOrThrow()

// 4. 내 정보 업데이트
setMyUserUseCase(
userName = user.username,
profileImageUrl = "${BuildConfig.api_key}/${imagePath}"
).getOrThrow()

}
}
2 changes: 2 additions & 0 deletions domain/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import java.util.Properties
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
alias(libs.plugins.kotlin.serialization)
}

val properties = Properties()
Expand Down Expand Up @@ -53,5 +54,6 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.converter.gson)

}
15 changes: 15 additions & 0 deletions domain/src/main/java/com/example/domain/model/Image.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.domain.model

import com.google.gson.annotations.SerializedName


data class Image(
@SerializedName("uri")
val uri: String,
@SerializedName("name")
val name: String,
@SerializedName("size")
val size: Long,
@SerializedName("mimeType")
val mimeType: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.domain.usecase.file

import com.example.domain.model.Image

interface GetImageUseCase {
operator fun invoke(contentUri: String) : Image?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.domain.usecase.file

import java.io.InputStream

interface GetInputStreamUseCase {
operator fun invoke(contentUri: String): Result<InputStream>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.domain.usecase.file

import com.example.domain.model.Image

interface UploadImageUseCase {
suspend operator fun invoke(
image: Image
): Result<String>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.domain.usecase.main.setting

interface SetMyUserUseCase {
suspend operator fun invoke(userName : String? = null, profileImageUrl: String? = null) : Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.domain.usecase.main.setting

interface SetProfileImageUseCase {
suspend operator fun invoke(contentUri: String) : Result<Unit>
}
Loading

0 comments on commit 11fb7b1

Please sign in to comment.