From 119301909f4eaf3a4b6e276e255bc9a96b274583 Mon Sep 17 00:00:00 2001 From: SangEun Date: Thu, 12 Sep 2024 00:32:51 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20UI,=20UseCas?= =?UTF-8?q?e=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/di/usecase/AuthUseCaseModule.kt | 10 ++++ .../request/ProfileImageUploadPayload.kt | 11 ++++ .../response/ProfileImageUploadResponse.kt | 12 +++++ .../com/dkin/chevit/data/remote/AuthAPI.kt | 5 ++ .../data/repository/AuthRepositoryImpl.kt | 17 ++++++ .../chevit/domain/model/ProfileImageData.kt | 10 ++++ .../com/dkin/chevit/domain/model/UserState.kt | 4 +- .../domain/repository/AuthRepository.kt | 3 ++ .../auth/GetProfileImageDataUseCase.kt | 18 +++++++ .../home/contents/HomeTabContents.kt | 2 +- .../home/contents/user/UserContents.kt | 2 +- .../user/profile/EditProfileImageContents.kt | 36 +++++++++---- .../contents/user/profile/ProfileSetting.kt | 22 +++++++- .../user/profile/ProfileSettingContract.kt | 6 +-- .../user/profile/ProfileSettingScreen.kt | 53 ++++++++++--------- .../user/profile/ProfileSettingViewModel.kt | 11 ++-- 16 files changed, 172 insertions(+), 50 deletions(-) create mode 100644 data/src/main/java/com/dkin/chevit/data/model/request/ProfileImageUploadPayload.kt create mode 100644 data/src/main/java/com/dkin/chevit/data/model/response/ProfileImageUploadResponse.kt create mode 100644 domain/src/main/java/com/dkin/chevit/domain/model/ProfileImageData.kt create mode 100644 domain/src/main/java/com/dkin/chevit/domain/usecase/auth/GetProfileImageDataUseCase.kt diff --git a/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt b/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt index a759805..ccc91d3 100644 --- a/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt +++ b/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt @@ -2,6 +2,7 @@ package com.dkin.chevit.data.di.usecase import com.dkin.chevit.domain.base.CoroutineDispatcherProvider import com.dkin.chevit.domain.repository.AuthRepository +import com.dkin.chevit.domain.usecase.auth.GetProfileImageDataUseCase import com.dkin.chevit.domain.usecase.auth.GetUserStateUseCase import com.dkin.chevit.domain.usecase.auth.GetUserUseCase import com.dkin.chevit.domain.usecase.auth.SignOutUseCase @@ -69,4 +70,13 @@ internal object AuthUseCaseModule { coroutineDispatcherProvider, authRepository ) + + @Provides + fun provideGetProfileImageDataUseCase( + coroutineDispatcherProvider: CoroutineDispatcherProvider, + authRepository: AuthRepository, + ) = GetProfileImageDataUseCase( + coroutineDispatcherProvider, + authRepository + ) } diff --git a/data/src/main/java/com/dkin/chevit/data/model/request/ProfileImageUploadPayload.kt b/data/src/main/java/com/dkin/chevit/data/model/request/ProfileImageUploadPayload.kt new file mode 100644 index 0000000..80b2779 --- /dev/null +++ b/data/src/main/java/com/dkin/chevit/data/model/request/ProfileImageUploadPayload.kt @@ -0,0 +1,11 @@ +package com.dkin.chevit.data.model.request + +import com.dkin.chevit.data.DataModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class ProfileImageUploadPayload( + @SerialName("fileSize") val fileSize: Int, + @SerialName("mimeType") val mimeType: String, +) : DataModel diff --git a/data/src/main/java/com/dkin/chevit/data/model/response/ProfileImageUploadResponse.kt b/data/src/main/java/com/dkin/chevit/data/model/response/ProfileImageUploadResponse.kt new file mode 100644 index 0000000..eb2f0d9 --- /dev/null +++ b/data/src/main/java/com/dkin/chevit/data/model/response/ProfileImageUploadResponse.kt @@ -0,0 +1,12 @@ +package com.dkin.chevit.data.model.response + +import com.dkin.chevit.data.DataModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +internal data class ProfileImageUploadResponse( + @SerialName("uploadMethod") val uploadMethod: String = "", + @SerialName("uploadURL") val uploadURL: String = "", + @SerialName("imageURL") val imageURL: String = "", +) : DataModel \ No newline at end of file diff --git a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt index 8e7a611..b4913de 100644 --- a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt +++ b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt @@ -1,8 +1,10 @@ package com.dkin.chevit.data.remote +import com.dkin.chevit.data.model.request.ProfileImageUploadPayload import com.dkin.chevit.data.model.request.SignUpPayload import com.dkin.chevit.data.model.request.UpdateUserPayload import com.dkin.chevit.data.model.request.ValidationNicknamePayload +import com.dkin.chevit.data.model.response.ProfileImageUploadResponse import com.dkin.chevit.data.model.response.UserResponse import retrofit2.Response import retrofit2.http.Body @@ -29,4 +31,7 @@ internal interface AuthAPI { @DELETE("deleteUser") suspend fun deleteUser(): Response + + @POST("getProfileUploadURL") + suspend fun getProfileUploadURL(@Body body: ProfileImageUploadPayload): ProfileImageUploadResponse } diff --git a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt index 251a7b5..55931bf 100644 --- a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt @@ -1,9 +1,11 @@ package com.dkin.chevit.data.repository +import com.dkin.chevit.data.model.request.ProfileImageUploadPayload import com.dkin.chevit.data.model.request.SignUpPayload import com.dkin.chevit.data.model.request.UpdateUserPayload import com.dkin.chevit.data.model.response.toUser import com.dkin.chevit.data.remote.AuthAPI +import com.dkin.chevit.domain.model.ProfileImageData import com.dkin.chevit.domain.model.UserState import com.dkin.chevit.domain.repository.AuthRepository import com.google.firebase.auth.FirebaseAuth @@ -43,4 +45,19 @@ internal class AuthRepositoryImpl @Inject constructor( auth.signOut() return getUserState() } + + override suspend fun getProfileUploadURL(fileSize: Int): ProfileImageData { + val result = authAPI.getProfileUploadURL( + ProfileImageUploadPayload( + fileSize = fileSize, + mimeType = "image/png" + ) + ) + return ProfileImageData( + uploadMethod = result.uploadMethod, + uploadURL = result.uploadURL, + uploadHeaders = "{\"Content-Type\": [\"image/png\"]}", + imageURL = result.imageURL, + ) + } } diff --git a/domain/src/main/java/com/dkin/chevit/domain/model/ProfileImageData.kt b/domain/src/main/java/com/dkin/chevit/domain/model/ProfileImageData.kt new file mode 100644 index 0000000..f12b1e1 --- /dev/null +++ b/domain/src/main/java/com/dkin/chevit/domain/model/ProfileImageData.kt @@ -0,0 +1,10 @@ +package com.dkin.chevit.domain.model + +import com.dkin.chevit.domain.base.DomainModel + +data class ProfileImageData( + val uploadMethod: String, + val uploadURL: String, + val uploadHeaders: String, + val imageURL: String +) : DomainModel \ No newline at end of file diff --git a/domain/src/main/java/com/dkin/chevit/domain/model/UserState.kt b/domain/src/main/java/com/dkin/chevit/domain/model/UserState.kt index f0affd4..498cb04 100644 --- a/domain/src/main/java/com/dkin/chevit/domain/model/UserState.kt +++ b/domain/src/main/java/com/dkin/chevit/domain/model/UserState.kt @@ -3,9 +3,9 @@ package com.dkin.chevit.domain.model import com.dkin.chevit.domain.base.DomainModel sealed interface UserState : DomainModel { - object Guest : UserState + data object Guest : UserState - object NotRegister : UserState + data object NotRegister : UserState data class User( val id: String, diff --git a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt index 28c225e..2add51f 100644 --- a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt @@ -1,5 +1,6 @@ package com.dkin.chevit.domain.repository +import com.dkin.chevit.domain.model.ProfileImageData import com.dkin.chevit.domain.model.UserState interface AuthRepository { @@ -12,4 +13,6 @@ interface AuthRepository { suspend fun signOutUser(): UserState suspend fun withDrawUser(): UserState + + suspend fun getProfileUploadURL(fileSize: Int): ProfileImageData } diff --git a/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/GetProfileImageDataUseCase.kt b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/GetProfileImageDataUseCase.kt new file mode 100644 index 0000000..a108064 --- /dev/null +++ b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/GetProfileImageDataUseCase.kt @@ -0,0 +1,18 @@ +package com.dkin.chevit.domain.usecase.auth + +import com.dkin.chevit.domain.base.CoroutineDispatcherProvider +import com.dkin.chevit.domain.base.IOUseCase +import com.dkin.chevit.domain.model.ProfileImageData +import com.dkin.chevit.domain.repository.AuthRepository + +class GetProfileImageDataUseCase( + coroutineDispatcherProvider: CoroutineDispatcherProvider, + private val authRepository: AuthRepository, +) : IOUseCase(coroutineDispatcherProvider = coroutineDispatcherProvider) { + override suspend fun execute(params: Param): ProfileImageData { + return authRepository.getProfileUploadURL(params.fileSize) + } + + @JvmInline + value class Param(val fileSize: Int) +} \ No newline at end of file diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt index 0c1c0e8..5728f22 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/HomeTabContents.kt @@ -238,7 +238,7 @@ private fun HomeStable( .crossfade(true) .build(), contentDescription = "", - contentScale = ContentScale.Fit, + contentScale = ContentScale.FillBounds, error = painterResource(id = R.drawable.ic_profile_empty) ) } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/UserContents.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/UserContents.kt index 1aac181..e34c278 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/UserContents.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/UserContents.kt @@ -103,7 +103,7 @@ fun UserContents( .crossfade(true) .build(), contentDescription = "", - contentScale = ContentScale.Fit, + contentScale = ContentScale.FillBounds, error = painterResource(id = drawable.ic_profile_empty) ) } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/EditProfileImageContents.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/EditProfileImageContents.kt index e3943e2..cfe30a8 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/EditProfileImageContents.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/EditProfileImageContents.kt @@ -1,5 +1,8 @@ package com.dkin.chevit.presentation.home.contents.user.profile +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -8,9 +11,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.dkin.chevit.presentation.resource.ChevitBottomsheet @@ -18,19 +23,29 @@ import com.dkin.chevit.presentation.resource.ChevitTheme @Composable fun EditProfileImageContents( - viewModel: ProfileSettingViewModel, onClickBack: () -> Unit, + changeImage: (uri: Uri?) -> Unit ) { + val launcher = rememberLauncherForActivityResult( + contract = + ActivityResultContracts.GetContent() + ) { uri: Uri? -> + changeImage(uri) + onClickBack() + } + ChevitBottomsheet( modifier = Modifier.fillMaxSize(), onClickBackground = onClickBack ) { Column(modifier = Modifier.fillMaxWidth()) { Column( - modifier = Modifier.fillMaxWidth().clickable { - viewModel.openAlbum() - onClickBack() - } + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .clickable { + launcher.launch("image/*") + } ) { Spacer(modifier = Modifier.height(16.dp)) Text( @@ -48,10 +63,13 @@ fun EditProfileImageContents( .background(color = ChevitTheme.colors.grey0) ) Column( - modifier = Modifier.fillMaxWidth().clickable { - viewModel.resetProfileImage() - onClickBack() - } + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .clickable { + changeImage(null) + onClickBack() + } ) { Spacer(modifier = Modifier.height(16.dp)) Text( diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt index 9fd59b0..2982b41 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt @@ -4,10 +4,16 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.viewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.dialog @@ -40,10 +46,22 @@ class ProfileSetting : setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { val navController = rememberNavController() + val settingState by viewModel.state.collectAsStateWithLifecycle() + var imageUrl by remember { mutableStateOf("") } + + LaunchedEffect(settingState) { + val state = settingState + if (state is ProfileSettingState.Stable) { + imageUrl = state.imageUrl + } + } + NavHost(navController = navController, startDestination = "settingMain") { composable("settingMain") { ProfileSettingScreen( viewModel = viewModel, + settingState = settingState, + imageUrl = imageUrl, onClickBack = { findNavController().popBackStack() }, onClickImage = { navController.navigate("editImage") } ) @@ -53,8 +71,8 @@ class ProfileSetting : dialogProperties = DialogProperties(usePlatformDefaultWidth = false) ) { EditProfileImageContents( - viewModel = viewModel, - onClickBack = { navController.popBackStack() } + onClickBack = { navController.popBackStack() }, + changeImage = { uri -> imageUrl = uri.toString() } ) } } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt index 46cd3f6..6399088 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt @@ -6,13 +6,13 @@ import com.dkin.chevit.core.mvi.ViewIntent import com.dkin.chevit.core.mvi.ViewState sealed interface ProfileSettingIntent : ViewIntent { - object Initialize : ProfileSettingIntent + data object Initialize : ProfileSettingIntent data class SaveProfile(val name: String, val imageUrl: String) : ProfileSettingIntent } @Stable sealed interface ProfileSettingState : ViewState { - object Loading : ProfileSettingState + data object Loading : ProfileSettingState data class Stable( val name: String, val imageUrl: String @@ -25,5 +25,5 @@ sealed interface ProfileSettingState : ViewState { sealed interface ProfileSettingEffect : ViewEffect { - object NavPopBack : ProfileSettingEffect + data object NavPopBack : ProfileSettingEffect } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt index 238ab1c..f2d340b 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt @@ -1,5 +1,6 @@ package com.dkin.chevit.presentation.home.contents.user.profile +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -13,7 +14,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -34,16 +34,18 @@ import com.dkin.chevit.presentation.resource.ChevitTheme import com.dkin.chevit.presentation.resource.R import com.dkin.chevit.presentation.resource.icon.ChevitIcon import com.dkin.chevit.presentation.resource.icon.IconArrowLeftLine +import com.dkin.chevit.presentation.resource.icon.IconCameraFill import com.dkin.chevit.presentation.resource.icon.IconCloseCircleFill import com.dkin.chevit.presentation.resource.util.clickableNoRipple @Composable fun ProfileSettingScreen( viewModel: ProfileSettingViewModel, + settingState: ProfileSettingState, + imageUrl: String, onClickBack: () -> Unit, onClickImage: () -> Unit, ) { - val settingState by viewModel.state.collectAsState() LaunchedEffect(Unit) { viewModel.dispatch(ProfileSettingIntent.Initialize) @@ -73,7 +75,7 @@ fun ProfileSettingScreen( } Spacer(modifier = Modifier.height(92.dp)) - when (val state = settingState) { + when (settingState) { ProfileSettingState.Loading -> { Column( modifier = Modifier @@ -84,12 +86,12 @@ fun ProfileSettingScreen( } is ProfileSettingState.Stable -> { - var name by remember { mutableStateOf(state.name) } - var imageUrl by remember { mutableStateOf(state.imageUrl) } + var name by remember { mutableStateOf(settingState.name) } var isValidInput by remember { mutableStateOf(false) } LaunchedEffect(name) { - isValidInput = name.isNotBlank() && name.length < 8 && imageUrl.isNotBlank() + isValidInput = + name.isNotBlank() && name.length < 8 && settingState.imageUrl.isNotBlank() } Column( @@ -98,7 +100,7 @@ fun ProfileSettingScreen( .weight(1f), horizontalAlignment = Alignment.CenterHorizontally ) { - Box(modifier = Modifier.clickableNoRipple { /*onClickImage()*/ }) { + Box(modifier = Modifier.clickableNoRipple { onClickImage() }) { Box( modifier = Modifier .size(128.dp) @@ -112,25 +114,25 @@ fun ProfileSettingScreen( .crossfade(true) .build(), contentDescription = "", - contentScale = ContentScale.Fit, + contentScale = ContentScale.FillBounds, error = painterResource(id = R.drawable.ic_profile_empty) ) } -// Box( -// modifier = Modifier -// .size(32.dp) -// .clip(CircleShape) -// .background(color = ChevitTheme.colors.black) -// .align(Alignment.BottomEnd) -// .padding(4.dp), -// contentAlignment = Alignment.Center -// ) { -// Icon( -// imageVector = ChevitIcon.IconCameraFill, -// contentDescription = "", -// tint = ChevitTheme.colors.white -// ) -// } + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(color = ChevitTheme.colors.black) + .align(Alignment.BottomEnd) + .padding(4.dp), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = ChevitIcon.IconCameraFill, + contentDescription = "", + tint = ChevitTheme.colors.white + ) + } } Spacer(modifier = Modifier.height(32.dp)) @@ -179,7 +181,10 @@ fun ProfileSettingScreen( modifier = Modifier.fillMaxWidth(), enabled = isValidInput, onClick = { - viewModel.dispatch(ProfileSettingIntent.SaveProfile(name, imageUrl)) + viewModel.dispatch(ProfileSettingIntent.SaveProfile( + name, + imageUrl + )) } ) { Text(text = "저장하기") diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt index 4416349..0e13c76 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt @@ -3,6 +3,7 @@ package com.dkin.chevit.presentation.home.contents.user.profile import com.dkin.chevit.core.mvi.MVIViewModel import com.dkin.chevit.domain.base.get import com.dkin.chevit.domain.base.onComplete +import com.dkin.chevit.domain.usecase.auth.GetProfileImageDataUseCase import com.dkin.chevit.domain.usecase.auth.GetUserUseCase import com.dkin.chevit.domain.usecase.auth.UpdateUserUseCase import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.Initialize @@ -14,6 +15,7 @@ import javax.inject.Inject class ProfileSettingViewModel @Inject constructor( private val getUserUseCase: GetUserUseCase, private val updateUserUseCase: UpdateUserUseCase, + private val getProfileImageDataUseCase: GetProfileImageDataUseCase ) : MVIViewModel() { override fun createInitialState(): ProfileSettingState = ProfileSettingState.Loading @@ -36,6 +38,7 @@ class ProfileSettingViewModel @Inject constructor( } private suspend fun saveProfile(name: String, imageUrl: String) { + //TODO getProfileImageDataUseCase > url 받아와서 updateUser val param = UpdateUserUseCase.Params( name = name.takeIf { it.isNotBlank() }, profileImage = imageUrl.takeIf { it.isNotBlank() } @@ -44,12 +47,4 @@ class ProfileSettingViewModel @Inject constructor( setEffect { ProfileSettingEffect.NavPopBack } } } - - fun openAlbum() { - //TODO - } - - fun resetProfileImage() { - //TODO - } } From fbd8c9bdd109dae8f4bdf2e372cbbfc1b759cb40 Mon Sep 17 00:00:00 2001 From: SangEun Date: Thu, 12 Sep 2024 23:56:43 +0900 Subject: [PATCH 2/5] =?UTF-8?q?image=20upload=20api=20=EA=B5=AC=ED=98=84(?= =?UTF-8?q?=EB=AF=B8=EC=99=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/di/usecase/AuthUseCaseModule.kt | 10 +++ .../com/dkin/chevit/data/remote/AuthAPI.kt | 12 ++++ .../data/repository/AuthRepositoryImpl.kt | 27 ++++++- .../domain/repository/AuthRepository.kt | 4 ++ .../usecase/auth/UploadProfileImageUseCase.kt | 28 ++++++++ .../contents/user/profile/ProfileSetting.kt | 7 +- .../user/profile/ProfileSettingContract.kt | 2 +- .../user/profile/ProfileSettingScreen.kt | 4 +- .../user/profile/ProfileSettingViewModel.kt | 70 +++++++++++++++++-- 9 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt diff --git a/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt b/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt index ccc91d3..12b4af5 100644 --- a/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt +++ b/data/src/main/java/com/dkin/chevit/data/di/usecase/AuthUseCaseModule.kt @@ -8,6 +8,7 @@ import com.dkin.chevit.domain.usecase.auth.GetUserUseCase import com.dkin.chevit.domain.usecase.auth.SignOutUseCase import com.dkin.chevit.domain.usecase.auth.SignUpUserUseCase import com.dkin.chevit.domain.usecase.auth.UpdateUserUseCase +import com.dkin.chevit.domain.usecase.auth.UploadProfileImageUseCase import com.dkin.chevit.domain.usecase.auth.WithDrawUserUseCase import dagger.Module import dagger.Provides @@ -79,4 +80,13 @@ internal object AuthUseCaseModule { coroutineDispatcherProvider, authRepository ) + + @Provides + fun provideUploadProfileImageUseCase( + coroutineDispatcherProvider: CoroutineDispatcherProvider, + authRepository: AuthRepository, + ) = UploadProfileImageUseCase( + coroutineDispatcherProvider, + authRepository + ) } diff --git a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt index b4913de..2e8d568 100644 --- a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt +++ b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt @@ -6,12 +6,17 @@ import com.dkin.chevit.data.model.request.UpdateUserPayload import com.dkin.chevit.data.model.request.ValidationNicknamePayload import com.dkin.chevit.data.model.response.ProfileImageUploadResponse import com.dkin.chevit.data.model.response.UserResponse +import com.dkin.chevit.domain.base.None +import okhttp3.MultipartBody import retrofit2.Response import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.Multipart import retrofit2.http.POST import retrofit2.http.PUT +import retrofit2.http.Part +import retrofit2.http.Url /** * 유저 정보 및 인증 관련 API 모음 @@ -34,4 +39,11 @@ internal interface AuthAPI { @POST("getProfileUploadURL") suspend fun getProfileUploadURL(@Body body: ProfileImageUploadPayload): ProfileImageUploadResponse + + @Multipart + @POST + suspend fun uploadProfileImage( + @Url url: String, + @Part image: MultipartBody.Part + ): None } diff --git a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt index 55931bf..d3adc34 100644 --- a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt @@ -5,15 +5,21 @@ import com.dkin.chevit.data.model.request.SignUpPayload import com.dkin.chevit.data.model.request.UpdateUserPayload import com.dkin.chevit.data.model.response.toUser import com.dkin.chevit.data.remote.AuthAPI +import com.dkin.chevit.domain.base.None import com.dkin.chevit.domain.model.ProfileImageData import com.dkin.chevit.domain.model.UserState import com.dkin.chevit.domain.repository.AuthRepository import com.google.firebase.auth.FirebaseAuth +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File import javax.inject.Inject internal class AuthRepositoryImpl @Inject constructor( private val authAPI: AuthAPI, - private val auth: FirebaseAuth + private val auth: FirebaseAuth, ) : AuthRepository { override suspend fun getUserState(): UserState { return runCatching { @@ -50,14 +56,29 @@ internal class AuthRepositoryImpl @Inject constructor( val result = authAPI.getProfileUploadURL( ProfileImageUploadPayload( fileSize = fileSize, - mimeType = "image/png" + mimeType = "image/jpeg" ) ) return ProfileImageData( uploadMethod = result.uploadMethod, uploadURL = result.uploadURL, - uploadHeaders = "{\"Content-Type\": [\"image/png\"]}", + uploadHeaders = "{\"Content-Type\": [\"image/jpeg\"]}", imageURL = result.imageURL, ) } + + override suspend fun uploadProfileImage( + uploadURL: String, + uploadMethod: String, + uploadHeaders: String, + file: File + ): None { + val requestFile: RequestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull()) + val uploadFile = MultipartBody.Part.createFormData( + "bgImages", + "profile.jpg", + requestFile + ) + return authAPI.uploadProfileImage(uploadURL, uploadFile) + } } diff --git a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt index 2add51f..d18bf9c 100644 --- a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt @@ -1,7 +1,9 @@ package com.dkin.chevit.domain.repository +import com.dkin.chevit.domain.base.None import com.dkin.chevit.domain.model.ProfileImageData import com.dkin.chevit.domain.model.UserState +import java.io.File interface AuthRepository { suspend fun getUserState(): UserState @@ -15,4 +17,6 @@ interface AuthRepository { suspend fun withDrawUser(): UserState suspend fun getProfileUploadURL(fileSize: Int): ProfileImageData + + suspend fun uploadProfileImage(uploadURL: String, uploadMethod: String, uploadHeaders: String, file: File): None } diff --git a/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt new file mode 100644 index 0000000..a6fc62b --- /dev/null +++ b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt @@ -0,0 +1,28 @@ +package com.dkin.chevit.domain.usecase.auth + +import com.dkin.chevit.domain.base.CoroutineDispatcherProvider +import com.dkin.chevit.domain.base.IOUseCase +import com.dkin.chevit.domain.base.None +import com.dkin.chevit.domain.repository.AuthRepository +import java.io.File + +class UploadProfileImageUseCase( + coroutineDispatcherProvider: CoroutineDispatcherProvider, + private val authRepository: AuthRepository, +) : IOUseCase(coroutineDispatcherProvider = coroutineDispatcherProvider) { + override suspend fun execute(params: Param): None { + return authRepository.uploadProfileImage( + uploadURL = params.uploadURL, + uploadMethod = params.uploadMethod, + uploadHeaders = params.uploadHeaders, + file = params.file + ) + } + + data class Param( + val uploadURL: String, + val uploadMethod: String, + val uploadHeaders: String, + val file: File + ) +} \ No newline at end of file diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt index 2982b41..933b5c5 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt @@ -48,6 +48,7 @@ class ProfileSetting : val navController = rememberNavController() val settingState by viewModel.state.collectAsStateWithLifecycle() var imageUrl by remember { mutableStateOf("") } + var imageChanged by remember { mutableStateOf(false) } LaunchedEffect(settingState) { val state = settingState @@ -62,6 +63,7 @@ class ProfileSetting : viewModel = viewModel, settingState = settingState, imageUrl = imageUrl, + imageChanged = imageChanged, onClickBack = { findNavController().popBackStack() }, onClickImage = { navController.navigate("editImage") } ) @@ -72,7 +74,10 @@ class ProfileSetting : ) { EditProfileImageContents( onClickBack = { navController.popBackStack() }, - changeImage = { uri -> imageUrl = uri.toString() } + changeImage = { uri -> + imageUrl = uri.toString() + imageChanged = true + } ) } } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt index 6399088..65c6798 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt @@ -7,7 +7,7 @@ import com.dkin.chevit.core.mvi.ViewState sealed interface ProfileSettingIntent : ViewIntent { data object Initialize : ProfileSettingIntent - data class SaveProfile(val name: String, val imageUrl: String) : ProfileSettingIntent + data class SaveProfile(val name: String, val imageUrl: String, val isNewImage: Boolean) : ProfileSettingIntent } @Stable diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt index f2d340b..f00f360 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt @@ -43,6 +43,7 @@ fun ProfileSettingScreen( viewModel: ProfileSettingViewModel, settingState: ProfileSettingState, imageUrl: String, + imageChanged: Boolean, onClickBack: () -> Unit, onClickImage: () -> Unit, ) { @@ -183,7 +184,8 @@ fun ProfileSettingScreen( onClick = { viewModel.dispatch(ProfileSettingIntent.SaveProfile( name, - imageUrl + imageUrl, + imageChanged )) } ) { diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt index 0e13c76..f413efd 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt @@ -1,21 +1,27 @@ package com.dkin.chevit.presentation.home.contents.user.profile +import androidx.core.net.toUri +import androidx.lifecycle.viewModelScope import com.dkin.chevit.core.mvi.MVIViewModel import com.dkin.chevit.domain.base.get import com.dkin.chevit.domain.base.onComplete import com.dkin.chevit.domain.usecase.auth.GetProfileImageDataUseCase import com.dkin.chevit.domain.usecase.auth.GetUserUseCase import com.dkin.chevit.domain.usecase.auth.UpdateUserUseCase +import com.dkin.chevit.domain.usecase.auth.UploadProfileImageUseCase import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.Initialize import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.SaveProfile import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import java.io.File import javax.inject.Inject @HiltViewModel class ProfileSettingViewModel @Inject constructor( private val getUserUseCase: GetUserUseCase, private val updateUserUseCase: UpdateUserUseCase, - private val getProfileImageDataUseCase: GetProfileImageDataUseCase + private val getProfileImageDataUseCase: GetProfileImageDataUseCase, + private val uploadProfileImageUseCase: UploadProfileImageUseCase ) : MVIViewModel() { override fun createInitialState(): ProfileSettingState = ProfileSettingState.Loading @@ -23,7 +29,7 @@ class ProfileSettingViewModel @Inject constructor( override suspend fun processIntent(intent: ProfileSettingIntent) { when (intent) { Initialize -> getProfile() - is SaveProfile -> saveProfile(intent.name, intent.imageUrl) + is SaveProfile -> saveProfile(intent.name, intent.imageUrl, intent.isNewImage) } } @@ -37,8 +43,36 @@ class ProfileSettingViewModel @Inject constructor( } } - private suspend fun saveProfile(name: String, imageUrl: String) { - //TODO getProfileImageDataUseCase > url 받아와서 updateUser + private suspend fun saveProfile(name: String, imageUrl: String, isNewImage: Boolean) { + if (isNewImage) { + kotlin.runCatching { + val imageUri = imageUrl.toUri() + val filePath = imageUri.path ?: "" + val imageFile = File(filePath) + getProfileImageDataUseCase( + params = GetProfileImageDataUseCase.Param( + fileSize = imageFile.length().toInt() + ) + ).onComplete { + saveImageWithUpdateProfile( + name = name, + imageUrl = imageUrl, + newImageUrl = imageURL, + uploadURL = uploadURL, + uploadMethod = uploadMethod, + uploadHeaders = uploadHeaders, + file = imageFile + ) + } + }.onFailure { + updateProfile(name, imageUrl) + } + } else { + updateProfile(name, imageUrl) + } + } + + private suspend fun updateProfile(name: String, imageUrl: String) { val param = UpdateUserUseCase.Params( name = name.takeIf { it.isNotBlank() }, profileImage = imageUrl.takeIf { it.isNotBlank() } @@ -47,4 +81,32 @@ class ProfileSettingViewModel @Inject constructor( setEffect { ProfileSettingEffect.NavPopBack } } } + + private fun saveImageWithUpdateProfile( + name: String, + imageUrl: String, + file: File, + newImageUrl: String, + uploadURL: String, + uploadMethod: String, + uploadHeaders: String + ) { + viewModelScope.launch { + kotlin.runCatching { + uploadProfileImageUseCase( + params = UploadProfileImageUseCase.Param( + uploadURL = uploadURL, + uploadMethod = uploadMethod, + uploadHeaders = uploadHeaders, + file = file + ) + ) + }.onSuccess { + updateProfile(name, newImageUrl) + }.onFailure { + updateProfile(name, imageUrl) + } + } + + } } From 3fad39f59aac7623246d1e5756a102101861fa8f Mon Sep 17 00:00:00 2001 From: SangEun Date: Thu, 3 Oct 2024 16:16:18 +0900 Subject: [PATCH 3/5] =?UTF-8?q?file=20provider=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 10 ++++ app/src/main/res/xml/file_paths.xml | 21 ++++++++ .../contents/user/profile/ProfileSetting.kt | 50 ++++++++++++++++++- .../user/profile/ProfileSettingContract.kt | 4 +- .../user/profile/ProfileSettingScreen.kt | 8 +-- .../user/profile/ProfileSettingViewModel.kt | 46 ++++++++--------- 6 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 app/src/main/res/xml/file_paths.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8b32b80..c04d8d5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,16 @@ android:value="androidx.startup" /> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt index 933b5c5..f14ad28 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSetting.kt @@ -1,6 +1,7 @@ package com.dkin.chevit.presentation.home.contents.user.profile import android.os.Bundle +import android.os.FileUtils.copy import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,6 +13,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.window.DialogProperties +import androidx.core.content.FileProvider +import androidx.core.net.toUri import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost @@ -23,6 +26,9 @@ import com.dkin.chevit.core.mvi.MVIComposeFragment import com.dkin.chevit.presentation.deeplink.navPopBack import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingEffect.NavPopBack import dagger.hilt.android.AndroidEntryPoint +import java.io.File +import java.io.FileOutputStream + @AndroidEntryPoint class ProfileSetting : @@ -63,9 +69,15 @@ class ProfileSetting : viewModel = viewModel, settingState = settingState, imageUrl = imageUrl, - imageChanged = imageChanged, onClickBack = { findNavController().popBackStack() }, - onClickImage = { navController.navigate("editImage") } + onClickImage = { navController.navigate("editImage") }, + onClickSave = { name -> + saveProfileSetting( + name, + imageUrl, + imageChanged + ) + } ) } dialog( @@ -84,4 +96,38 @@ class ProfileSetting : } } } + + private fun saveProfileSetting(name: String, imageUrl: String, imageChanged: Boolean) { + val directory = File(requireContext().cacheDir, "images") + directory.mkdirs() // 임시 파일이 위치할 폴더를 생성한다. + + if (imageChanged) { + val file = File.createTempFile( + "selected_image", + ".jpg", + directory, + ) // 해당 폴더에 임시 파일을 만든다. + + val authority = requireContext().packageName + ".fileprovider" // + val outputFileUri = FileProvider.getUriForFile(requireContext(), authority, file) + + FileOutputStream(file).use { outputStream -> + requireContext().contentResolver.openInputStream(imageUrl.toUri()) + .use { inputStream -> + inputStream?.copyTo(outputStream) + outputStream.flush() + } + } + viewModel.dispatch(ProfileSettingIntent.SaveImageProfile( + name, + outputFileUri.toString(), + file + )) + } else { + viewModel.dispatch(ProfileSettingIntent.SaveProfile( + name, + imageUrl, + )) + } + } } diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt index 65c6798..f42341b 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingContract.kt @@ -4,10 +4,12 @@ import androidx.compose.runtime.Stable import com.dkin.chevit.core.mvi.ViewEffect import com.dkin.chevit.core.mvi.ViewIntent import com.dkin.chevit.core.mvi.ViewState +import java.io.File sealed interface ProfileSettingIntent : ViewIntent { data object Initialize : ProfileSettingIntent - data class SaveProfile(val name: String, val imageUrl: String, val isNewImage: Boolean) : ProfileSettingIntent + data class SaveImageProfile(val name: String, val imageUrl: String, val file: File) : ProfileSettingIntent + data class SaveProfile(val name: String, val imageUrl: String) : ProfileSettingIntent } @Stable diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt index f00f360..27efce6 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingScreen.kt @@ -43,9 +43,9 @@ fun ProfileSettingScreen( viewModel: ProfileSettingViewModel, settingState: ProfileSettingState, imageUrl: String, - imageChanged: Boolean, onClickBack: () -> Unit, onClickImage: () -> Unit, + onClickSave: (name: String) -> Unit, ) { LaunchedEffect(Unit) { @@ -182,11 +182,7 @@ fun ProfileSettingScreen( modifier = Modifier.fillMaxWidth(), enabled = isValidInput, onClick = { - viewModel.dispatch(ProfileSettingIntent.SaveProfile( - name, - imageUrl, - imageChanged - )) + onClickSave(name) } ) { Text(text = "저장하기") diff --git a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt index f413efd..f9a5703 100644 --- a/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt +++ b/presentation/home/src/main/java/com/dkin/chevit/presentation/home/contents/user/profile/ProfileSettingViewModel.kt @@ -1,6 +1,5 @@ package com.dkin.chevit.presentation.home.contents.user.profile -import androidx.core.net.toUri import androidx.lifecycle.viewModelScope import com.dkin.chevit.core.mvi.MVIViewModel import com.dkin.chevit.domain.base.get @@ -11,6 +10,7 @@ import com.dkin.chevit.domain.usecase.auth.UpdateUserUseCase import com.dkin.chevit.domain.usecase.auth.UploadProfileImageUseCase import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.Initialize import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.SaveProfile +import com.dkin.chevit.presentation.home.contents.user.profile.ProfileSettingIntent.SaveImageProfile import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import java.io.File @@ -29,7 +29,8 @@ class ProfileSettingViewModel @Inject constructor( override suspend fun processIntent(intent: ProfileSettingIntent) { when (intent) { Initialize -> getProfile() - is SaveProfile -> saveProfile(intent.name, intent.imageUrl, intent.isNewImage) + is SaveImageProfile -> saveProfileWithImage(intent.name, intent.imageUrl, intent.file) + is SaveProfile -> updateProfile(intent.name, intent.imageUrl) } } @@ -43,31 +44,24 @@ class ProfileSettingViewModel @Inject constructor( } } - private suspend fun saveProfile(name: String, imageUrl: String, isNewImage: Boolean) { - if (isNewImage) { - kotlin.runCatching { - val imageUri = imageUrl.toUri() - val filePath = imageUri.path ?: "" - val imageFile = File(filePath) - getProfileImageDataUseCase( - params = GetProfileImageDataUseCase.Param( - fileSize = imageFile.length().toInt() - ) - ).onComplete { - saveImageWithUpdateProfile( - name = name, - imageUrl = imageUrl, - newImageUrl = imageURL, - uploadURL = uploadURL, - uploadMethod = uploadMethod, - uploadHeaders = uploadHeaders, - file = imageFile - ) - } - }.onFailure { - updateProfile(name, imageUrl) + private suspend fun saveProfileWithImage(name: String, imageUrl: String, file: File,) { + kotlin.runCatching { + getProfileImageDataUseCase( + params = GetProfileImageDataUseCase.Param( + fileSize = file.length().toInt() + ) + ).onComplete { + saveImageWithUpdateProfile( + name = name, + imageUrl = imageUrl, + newImageUrl = imageURL, + uploadURL = uploadURL, + uploadMethod = uploadMethod, + uploadHeaders = uploadHeaders, + file = file + ) } - } else { + }.onFailure { updateProfile(name, imageUrl) } } From 69348fd72a05d000562083e13e5b794d1c3a5ca1 Mon Sep 17 00:00:00 2001 From: SangEun Date: Thu, 3 Oct 2024 16:34:30 +0900 Subject: [PATCH 4/5] =?UTF-8?q?image=20upload=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt | 6 +++--- .../com/dkin/chevit/data/repository/AuthRepositoryImpl.kt | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt index 2e8d568..9563ebb 100644 --- a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt +++ b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt @@ -8,6 +8,7 @@ import com.dkin.chevit.data.model.response.ProfileImageUploadResponse import com.dkin.chevit.data.model.response.UserResponse import com.dkin.chevit.domain.base.None import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.Response import retrofit2.http.Body import retrofit2.http.DELETE @@ -40,10 +41,9 @@ internal interface AuthAPI { @POST("getProfileUploadURL") suspend fun getProfileUploadURL(@Body body: ProfileImageUploadPayload): ProfileImageUploadResponse - @Multipart - @POST + @PUT suspend fun uploadProfileImage( @Url url: String, - @Part image: MultipartBody.Part + @Body file: RequestBody ): None } diff --git a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt index d3adc34..310e8f6 100644 --- a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt @@ -74,11 +74,6 @@ internal class AuthRepositoryImpl @Inject constructor( file: File ): None { val requestFile: RequestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull()) - val uploadFile = MultipartBody.Part.createFormData( - "bgImages", - "profile.jpg", - requestFile - ) - return authAPI.uploadProfileImage(uploadURL, uploadFile) + return authAPI.uploadProfileImage(uploadURL, requestFile) } } From 14871b101c52cf10d7bae5005e509303519cae7e Mon Sep 17 00:00:00 2001 From: SangEun Date: Thu, 3 Oct 2024 17:20:09 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/dkin/chevit/data/di/NetworkModule.kt | 27 +++++++++++++++++++ .../com/dkin/chevit/data/di/RetrofitModule.kt | 8 ++++++ .../com/dkin/chevit/data/remote/AuthAPI.kt | 6 ----- .../com/dkin/chevit/data/remote/ImageAPI.kt | 14 ++++++++++ .../data/repository/AuthRepositoryImpl.kt | 6 +++-- .../domain/repository/AuthRepository.kt | 2 +- .../usecase/auth/UploadProfileImageUseCase.kt | 3 ++- 7 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 data/src/main/java/com/dkin/chevit/data/remote/ImageAPI.kt diff --git a/data/src/debug/java/com/dkin/chevit/data/di/NetworkModule.kt b/data/src/debug/java/com/dkin/chevit/data/di/NetworkModule.kt index 3acc63d..45d3b67 100644 --- a/data/src/debug/java/com/dkin/chevit/data/di/NetworkModule.kt +++ b/data/src/debug/java/com/dkin/chevit/data/di/NetworkModule.kt @@ -23,6 +23,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.Retrofit +import javax.inject.Named @Module @InstallIn(SingletonComponent::class) @@ -86,6 +87,20 @@ internal object NetworkModule { .addInterceptor(tokenInterceptor) .build() + @Provides + @Singleton + @Named("Pure") + fun providePureOkHttpClient( + httpLoggingInterceptor: HttpLoggingInterceptor, + chuckerInterceptor: ChuckerInterceptor, + ) = OkHttpClient.Builder() + .connectTimeout(20, TimeUnit.SECONDS) + .readTimeout(20, TimeUnit.SECONDS) + .writeTimeout(20, TimeUnit.SECONDS) + .addInterceptor(httpLoggingInterceptor) + .addInterceptor(chuckerInterceptor) + .build() + @Provides @Singleton fun provideRetrofit( @@ -96,4 +111,16 @@ internal object NetworkModule { .addConverterFactory(jsonConverter) .baseUrl(BuildConfig.API_URL) .build() + + @Provides + @Singleton + @Named("Pure") + fun providePureRetrofit( + @Named("Pure") okHttpClient: OkHttpClient, + @JsonConverter jsonConverter: Converter.Factory, + ): Retrofit = Retrofit.Builder() + .client(okHttpClient) + .addConverterFactory(jsonConverter) + .baseUrl(BuildConfig.API_URL) + .build() } diff --git a/data/src/main/java/com/dkin/chevit/data/di/RetrofitModule.kt b/data/src/main/java/com/dkin/chevit/data/di/RetrofitModule.kt index 12c14c0..163b6d8 100644 --- a/data/src/main/java/com/dkin/chevit/data/di/RetrofitModule.kt +++ b/data/src/main/java/com/dkin/chevit/data/di/RetrofitModule.kt @@ -1,6 +1,7 @@ package com.dkin.chevit.data.di import com.dkin.chevit.data.remote.AuthAPI +import com.dkin.chevit.data.remote.ImageAPI import com.dkin.chevit.data.remote.NotificationAPI import com.dkin.chevit.data.remote.PlanAPI import com.dkin.chevit.data.remote.ServiceAPI @@ -10,6 +11,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton import retrofit2.Retrofit +import javax.inject.Named @Module @InstallIn(SingletonComponent::class) @@ -37,4 +39,10 @@ internal object RetrofitModule { fun providePlanAPI( retrofit: Retrofit ): PlanAPI = retrofit.create(PlanAPI::class.java) + + @Provides + @Singleton + fun provideImageAPI( + @Named("Pure") retrofit: Retrofit + ): ImageAPI = retrofit.create(ImageAPI::class.java) } diff --git a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt index 9563ebb..9f14e49 100644 --- a/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt +++ b/data/src/main/java/com/dkin/chevit/data/remote/AuthAPI.kt @@ -40,10 +40,4 @@ internal interface AuthAPI { @POST("getProfileUploadURL") suspend fun getProfileUploadURL(@Body body: ProfileImageUploadPayload): ProfileImageUploadResponse - - @PUT - suspend fun uploadProfileImage( - @Url url: String, - @Body file: RequestBody - ): None } diff --git a/data/src/main/java/com/dkin/chevit/data/remote/ImageAPI.kt b/data/src/main/java/com/dkin/chevit/data/remote/ImageAPI.kt new file mode 100644 index 0000000..7468a14 --- /dev/null +++ b/data/src/main/java/com/dkin/chevit/data/remote/ImageAPI.kt @@ -0,0 +1,14 @@ +package com.dkin.chevit.data.remote + +import okhttp3.RequestBody +import retrofit2.http.Body +import retrofit2.http.PUT +import retrofit2.http.Url + +internal interface ImageAPI { + @PUT + suspend fun uploadProfileImage( + @Url url: String, + @Body file: RequestBody + ) +} \ No newline at end of file diff --git a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt index 310e8f6..518b022 100644 --- a/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/dkin/chevit/data/repository/AuthRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.dkin.chevit.data.model.request.SignUpPayload import com.dkin.chevit.data.model.request.UpdateUserPayload import com.dkin.chevit.data.model.response.toUser import com.dkin.chevit.data.remote.AuthAPI +import com.dkin.chevit.data.remote.ImageAPI import com.dkin.chevit.domain.base.None import com.dkin.chevit.domain.model.ProfileImageData import com.dkin.chevit.domain.model.UserState @@ -19,6 +20,7 @@ import javax.inject.Inject internal class AuthRepositoryImpl @Inject constructor( private val authAPI: AuthAPI, + private val imageAPI: ImageAPI, private val auth: FirebaseAuth, ) : AuthRepository { override suspend fun getUserState(): UserState { @@ -72,8 +74,8 @@ internal class AuthRepositoryImpl @Inject constructor( uploadMethod: String, uploadHeaders: String, file: File - ): None { + ) { val requestFile: RequestBody = file.asRequestBody("image/jpeg".toMediaTypeOrNull()) - return authAPI.uploadProfileImage(uploadURL, requestFile) + return imageAPI.uploadProfileImage(uploadURL, requestFile) } } diff --git a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt index d18bf9c..0617abd 100644 --- a/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt +++ b/domain/src/main/java/com/dkin/chevit/domain/repository/AuthRepository.kt @@ -18,5 +18,5 @@ interface AuthRepository { suspend fun getProfileUploadURL(fileSize: Int): ProfileImageData - suspend fun uploadProfileImage(uploadURL: String, uploadMethod: String, uploadHeaders: String, file: File): None + suspend fun uploadProfileImage(uploadURL: String, uploadMethod: String, uploadHeaders: String, file: File) } diff --git a/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt index a6fc62b..baa4bad 100644 --- a/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt +++ b/domain/src/main/java/com/dkin/chevit/domain/usecase/auth/UploadProfileImageUseCase.kt @@ -11,12 +11,13 @@ class UploadProfileImageUseCase( private val authRepository: AuthRepository, ) : IOUseCase(coroutineDispatcherProvider = coroutineDispatcherProvider) { override suspend fun execute(params: Param): None { - return authRepository.uploadProfileImage( + authRepository.uploadProfileImage( uploadURL = params.uploadURL, uploadMethod = params.uploadMethod, uploadHeaders = params.uploadHeaders, file = params.file ) + return None } data class Param(