From 90e10cafebc85c7ae60c2c372d255e727bbb8c89 Mon Sep 17 00:00:00 2001 From: hsgo2430 Date: Wed, 26 Jun 2024 15:13:23 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EC=83=81=EC=A0=90=20=EA=B0=80=EA=B2=8C=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/di/network/AuthNetworkModule.kt | 10 + .../finalcheckstore/FinalCheckStoreScreen.kt | 329 ++++++++++++++++++ .../FinalCheckStoreScreenSideEffect.kt | 8 + .../FinalCheckStoreScreenState.kt | 22 ++ .../FinalCheckStoreScreenViewmodel.kt | 93 +++++ .../InsertDetailInfoScreen.kt | 5 +- .../InsertDetailInfoScreenState.kt | 16 +- .../InsertDetailInfoScreenViewmodel.kt | 3 +- .../operatingTime/OperationgTimeState.kt | 3 +- .../insertmaininfo/InsertBasicInfoScreen.kt | 43 ++- .../InsertBasicInfoScreenSideEffect.kt | 7 +- .../InsertBasicInfoScreenState.kt | 2 + .../InsertBasicInfoScreenViewModel.kt | 114 +++++- .../insertstore/navigator/InsertStoreRoute.kt | 4 +- .../navigator/InsertStoreRouteNavigator.kt | 27 +- .../signup/businessauth/BusinessAuthScreen.kt | 2 +- .../businessauth/BusinessAuthViewModel.kt | 17 +- core/src/main/res/values/strings.xml | 8 +- .../in/koreatech/koin/data/api/OwnerApi.kt | 4 + .../koreatech/koin/data/api/UploadUrlApi.kt | 3 + .../koin/data/api/auth/OwnerAuthApi.kt | 11 + .../koin/data/constant/URLConstant.kt | 2 + .../data/di/source/RemoteDataSourceModule.kt | 4 +- .../koreatech/koin/data/mapper/StoreMapper.kt | 29 +- .../repository/OwnerRegisterRepositoryImpl.kt | 45 +++ .../repository/PreSignedUrlRepositoryImpl.kt | 4 +- .../repository/UploadUrlRepositoryImpl.kt | 23 ++ .../response/store/StoreDayOffResponse.kt | 10 + .../response/store/StoreRegisterResponse.kt | 17 + .../source/remote/OwnerRemoteDataSource.kt | 11 +- .../remote/UploadUrlRemoteDataSource.kt | 2 + .../repository/OwnerRegisterRepository.kt | 15 + .../repository/PreSignedUrlRepository.kt | 2 +- .../domain/repository/UploadUrlRepository.kt | 6 + .../usecase/business/UploadFileUseCase.kt | 2 +- .../business/store/RegisterStoreUseCase.kt | 37 ++ .../GetMarketPreSignedUrlUseCase.kt | 16 + 37 files changed, 922 insertions(+), 34 deletions(-) create mode 100644 business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt create mode 100644 business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenSideEffect.kt create mode 100644 business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenState.kt create mode 100644 business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/api/auth/OwnerAuthApi.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/store/StoreDayOffResponse.kt create mode 100644 data/src/main/java/in/koreatech/koin/data/response/store/StoreRegisterResponse.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/business/store/RegisterStoreUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt diff --git a/business/src/main/java/in/koreatech/business/di/network/AuthNetworkModule.kt b/business/src/main/java/in/koreatech/business/di/network/AuthNetworkModule.kt index 1eb12fcf0..48a40a026 100644 --- a/business/src/main/java/in/koreatech/business/di/network/AuthNetworkModule.kt +++ b/business/src/main/java/in/koreatech/business/di/network/AuthNetworkModule.kt @@ -1,6 +1,7 @@ package `in`.koreatech.business.di.network import android.content.Context +import android.util.Log import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -16,6 +17,7 @@ import `in`.koreatech.koin.core.qualifier.ServerUrl import `in`.koreatech.koin.data.api.PreSignedUrlApi import `in`.koreatech.koin.data.api.UploadUrlApi import `in`.koreatech.koin.data.api.UserApi +import `in`.koreatech.koin.data.api.auth.OwnerAuthApi import `in`.koreatech.koin.data.api.auth.UserAuthApi import `in`.koreatech.koin.data.source.local.TokenLocalDataSource import kotlinx.coroutines.runBlocking @@ -169,6 +171,14 @@ object BusinessAuthNetworkModule { ): UploadUrlApi { return retrofit.create(UploadUrlApi::class.java) } + + @Provides + @Singleton + fun provideOwnerAuthApi( + @OwnerAuth retrofit: Retrofit + ):OwnerAuthApi { + return retrofit.create(OwnerAuthApi::class.java) + } } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt new file mode 100644 index 000000000..c57d8e74a --- /dev/null +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt @@ -0,0 +1,329 @@ +package `in`.koreatech.business.feature.insertstore.finalcheckstore + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import `in`.koreatech.business.feature.insertstore.selectcategory.InsertStoreProgressBar +import `in`.koreatech.business.ui.theme.ColorActiveButton +import `in`.koreatech.business.ui.theme.ColorMinor +import `in`.koreatech.business.ui.theme.ColorPrimary +import `in`.koreatech.business.ui.theme.ColorSecondary +import `in`.koreatech.koin.core.R +import `in`.koreatech.koin.core.toast.ToastUtil +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect + + +@Composable +fun FinalCheckStoreScreen( + modifier: Modifier = Modifier, + onBackPressed: () -> Unit, + navigateToFinishScreen: () -> Unit, + viewModel: FinalCheckStoreScreenViewModel = hiltViewModel() +){ + val state = viewModel.collectAsState().value + + FinalCheckStoreScreenImpl( + state = state, + goToFinishScreen = { + viewModel.registerStore() + } + ) + + HandleSideEffects(viewModel, navigateToFinishScreen) +} + + +@Composable +fun FinalCheckStoreScreenImpl( + modifier: Modifier = Modifier, + goToFinishScreen: () -> Unit = {}, + onBackPressed: () -> Unit = {}, + state: FinalCheckStoreScreenState = FinalCheckStoreScreenState() +) { + LazyColumn( + modifier = modifier + .fillMaxSize() + ) { + item { + Box( + modifier = Modifier + .padding(top = 56.dp, start = 10.dp, bottom = 18.dp) + .size(40.dp) + .clickable { + onBackPressed() + } + ) { + Image( + painter = painterResource(R.drawable.ic_arrow_left), + contentDescription = "backArrow", + modifier = modifier + .size(40.dp) + ) + } + } + + item { + Text( + modifier = Modifier.padding(top = 35.dp, start = 40.dp), + text = stringResource(id = R.string.insert_store), + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + } + + item { + Text( + modifier = Modifier.padding(top = 34.dp, start = 40.dp), + text = stringResource(id = R.string.insert_store_main_info), + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + + item { + InsertStoreProgressBar( + modifier, + 1f, + R.string.insert_store_check_store_info, + R.string.page_four + ) + } + + item { + NameTextField( + stringResource(id = R.string.category), + state.storeCategory.toString(), + paddingTopValue = 32.dp + ) + } + + item { + NameTextField( + stringResource(id = R.string.insert_store_store_name), + state.storeName, + paddingTopValue = 24.dp + ) + } + + item { + NameTextField( + stringResource(id = R.string.insert_store_store_address), + state.storeAddress, + paddingTopValue = 24.dp + ) + } + + item { + NameTextField( + stringResource(id = R.string.calling_number), + state.storePhoneNumber, + paddingTopValue = 24.dp + ) + } + + item { + NameTextField( + stringResource(id = R.string.delivery_fee), + state.storeDeliveryFee, + paddingTopValue = 24.dp + ) + } + + item { + Row( + modifier = Modifier + .padding(top = 24.dp) + .padding(horizontal = 32.dp) + .fillMaxWidth() + ) { + Text( + text = stringResource(id = R.string.operating_time), + fontSize = 14.sp, + color = ColorActiveButton, + fontWeight = FontWeight.Bold + ) + + Column( + modifier = Modifier + .padding(start = 30.dp) + ) { + state.operatingTimeList.forEach { item -> + Text( + text = if (item.closed) stringResource( + id = R.string.insert_store_closed_day, + item.dayOfWeek + ) + else stringResource( + id = R.string.insert_store_operating_time, + item.dayOfWeek, + item.openTime, + item.closeTime + ) + ) + } + } + } + } + + item { + NameTextField( + stringResource(id = R.string.other_info), + state.storeOtherInfo, + paddingTopValue = 24.dp + ) + } + + item { + Row( + modifier = Modifier + .padding(top = 40.dp) + .padding(horizontal = 32.dp) + ) { + CreateOptionCheckBox( + stringResource(id = R.string.delivery_available), + state.isDeliveryOk + ) + + Spacer(modifier = Modifier.weight(1f)) + + CreateOptionCheckBox( + stringResource(id = R.string.card_available), + state.isCardOk + ) + + Spacer(modifier = Modifier.weight(1f)) + + CreateOptionCheckBox( + stringResource(id = R.string.account_transfer_avilable), + state.isBankOk + ) + } + } + + item { + Button( + onClick = goToFinishScreen, + colors = ButtonDefaults.buttonColors(ColorPrimary), + shape = RectangleShape, + modifier = Modifier + .padding(top = 57.dp, start = 240.dp, end = 16.dp, bottom = 20.dp) + .height(38.dp) + .width(105.dp) + ) { + Text( + text = stringResource(id = R.string.next), + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = Color.White + ) + } + } + } + } + +@Composable +fun NameTextField( + textString: String = "", + outputString: String = "", + paddingTopValue: Dp = 10.dp +){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + .padding(top = paddingTopValue), + verticalAlignment = Alignment.CenterVertically + ){ + Text( + text = textString, + fontSize = 14.sp, + color = ColorActiveButton, + fontWeight = FontWeight.Bold + ) + + Text( + modifier = Modifier.padding(start = 26.dp), + text = outputString, + fontSize = 14.sp, + fontWeight = FontWeight.Bold + ) + + + } +} + +@Composable +fun CreateOptionCheckBox( + checkString: String = "", + checkValue: Boolean = true, +){ + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = if(checkValue) painterResource(R.drawable.ic_insert_store_checked_box) + else painterResource(id = R.drawable.ic_insert_store_unchecked_box), + contentDescription = "checkBox") + + Text( + modifier = Modifier.padding(start = 8.dp), + text = checkString, + color = if(checkValue) ColorSecondary else ColorMinor, + fontSize = 14.sp + ) + } +} + +@Composable +private fun HandleSideEffects(viewModel: FinalCheckStoreScreenViewModel, navigateToFinishScreen: () -> Unit) { + + viewModel.collectSideEffect { sideEffect -> + when (sideEffect) { + is FinalCheckStoreScreenSideEffect.GoToFinishScreen -> navigateToFinishScreen() + is FinalCheckStoreScreenSideEffect.GoToFinishScreen -> ToastUtil.getInstance().makeShort(R.string.insert_store_null_store_phone_number) + else -> {} + } + } +} + + +@Preview +@Composable +fun PreviewStartInsertScreen(){ + FinalCheckStoreScreenImpl( + modifier = Modifier + ) +} \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenSideEffect.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenSideEffect.kt new file mode 100644 index 000000000..fd82b5058 --- /dev/null +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenSideEffect.kt @@ -0,0 +1,8 @@ +package `in`.koreatech.business.feature.insertstore.finalcheckstore + +import `in`.koreatech.business.feature.insertstore.selectcategory.SelectCategoryScreenSideEffect + +sealed class FinalCheckStoreScreenSideEffect { + object GoToFinishScreen: FinalCheckStoreScreenSideEffect() + object FailRegisterStore: FinalCheckStoreScreenSideEffect() +} \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenState.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenState.kt new file mode 100644 index 000000000..d8e79d993 --- /dev/null +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenState.kt @@ -0,0 +1,22 @@ +package `in`.koreatech.business.feature.insertstore.finalcheckstore + +import android.net.Uri +import android.os.Parcelable +import `in`.koreatech.business.feature.insertstore.insertdetailinfo.operatingTime.OperatingTimeState +import kotlinx.parcelize.Parcelize + + +@Parcelize +data class FinalCheckStoreScreenState( + val storeCategory: Int = -1, + val storeName: String = "", + val storeAddress: String = "", + val storeImage: String = "", + val storePhoneNumber: String = "", + val storeDeliveryFee: String ="", + val storeOtherInfo: String = "", + val isDeliveryOk: Boolean = false, + val isCardOk: Boolean = false, + val isBankOk: Boolean = false, + val operatingTimeList: List = emptyList() +): Parcelable \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt new file mode 100644 index 000000000..5df7ca031 --- /dev/null +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt @@ -0,0 +1,93 @@ +package `in`.koreatech.business.feature.insertstore.finalcheckstore + +import android.util.Log +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.chargemap.compose.numberpicker.Hours +import dagger.hilt.android.lifecycle.HiltViewModel +import `in`.koreatech.business.feature.insertstore.insertdetailinfo.InsertDetailInfoScreenState +import `in`.koreatech.business.feature.insertstore.insertdetailinfo.operatingTime.OperatingTimeState +import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime +import `in`.koreatech.koin.domain.usecase.business.store.RegisterStoreUseCase +import kotlinx.coroutines.launch +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost +import org.orbitmvi.orbit.syntax.simple.intent +import org.orbitmvi.orbit.syntax.simple.postSideEffect +import org.orbitmvi.orbit.syntax.simple.reduce +import org.orbitmvi.orbit.viewmodel.container +import javax.inject.Inject + +@HiltViewModel +class FinalCheckStoreScreenViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val registerStoreUseCase : RegisterStoreUseCase +): ViewModel(), ContainerHost { + override val container: Container = + container(FinalCheckStoreScreenState(), savedStateHandle = savedStateHandle) { + + val storeInfoJson: InsertDetailInfoScreenState? = + savedStateHandle.get("storeInfo") + if (storeInfoJson != null) getStoreInfo(storeInfoJson) + } + + private fun getStoreInfo(storeInfo: InsertDetailInfoScreenState){ + intent{ + reduce { + state.copy( + storeCategory = storeInfo.storeCategory, + storeName = storeInfo.storeName, + storeAddress = storeInfo.storeAddress, + storeImage = storeInfo.storeImage, + storePhoneNumber = storeInfo.storePhoneNumber, + storeDeliveryFee = storeInfo.storeDeliveryFee, + storeOtherInfo = storeInfo.storeOtherInfo, + isDeliveryOk = storeInfo.isDeliveryOk, + isBankOk = storeInfo.isBankOk, + isCardOk = storeInfo.isCardOk, + operatingTimeList = storeInfo.operatingTimeList + ) + } + } + } + + fun registerStore(){ + intent { + viewModelScope.launch { + registerStoreUseCase( + name = state.storeName, + category = state.storeCategory, + address = state.storeAddress, + imageUri = state.storeImage, + phoneNumber = state.storePhoneNumber, + deliveryPrice = state.storeDeliveryFee, + description = state.storeOtherInfo, + operatingTime = state.operatingTimeList.toOperatingTimeList(), + isDeliveryOk = state.isDeliveryOk, + isCardOk = state.isCardOk, + isBankOk = state.isBankOk + ).onSuccess { + intent{ + postSideEffect(FinalCheckStoreScreenSideEffect.GoToFinishScreen) + } + }.onFailure { + intent{ + postSideEffect(FinalCheckStoreScreenSideEffect.FailRegisterStore) + } + } + } + } + } +} + +private fun List.toOperatingTimeList(): List { + return this.map { operatingTimeState -> + OperatingTime( + closeTime = operatingTimeState.closeTime, + closed = operatingTimeState.closed, + dayOfWeek = operatingTimeState.dayOfWeekEnglish, + openTime = operatingTimeState.openTime + ) + } +} diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreen.kt index 44bda4267..a93aa395b 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreen.kt @@ -1,6 +1,7 @@ package `in`.koreatech.business.feature.insertstore.insertdetailinfo +import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -153,7 +154,7 @@ fun InsertDetailInfoScreenImpl( item { Text( modifier = Modifier.padding(top = 34.dp, start = 40.dp), - text = stringResource(id = R.string.insert_store_main_info), + text = stringResource(id = R.string.insert_store_detail), fontSize = 18.sp, fontWeight = FontWeight.Bold ) @@ -164,7 +165,7 @@ fun InsertDetailInfoScreenImpl( } item { - NameTextField(stringResource(id = R.string.phone_number), storePhoneNumber, onStorePhoneNumberChange, 32.dp) + NameTextField(stringResource(id = R.string.calling_number), storePhoneNumber, onStorePhoneNumberChange, 32.dp) } item { diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenState.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenState.kt index 7ac64d2f5..2c80dd958 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenState.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenState.kt @@ -10,7 +10,7 @@ data class InsertDetailInfoScreenState ( val storeCategory: Int = -1, val storeName: String = "", val storeAddress: String = "", - val storeImage: Uri = Uri.EMPTY, + val storeImage: String = "", val storePhoneNumber: String = "", val storeDeliveryFee: String ="", val storeOtherInfo: String = "", @@ -18,13 +18,13 @@ data class InsertDetailInfoScreenState ( val isCardOk: Boolean = false, val isBankOk: Boolean = false, val operatingTimeList: List = listOf( - OperatingTimeState("00:00", false, "월", "00:00"), - OperatingTimeState("00:00", false, "화", "00:00"), - OperatingTimeState("00:00", false, "수", "00:00"), - OperatingTimeState("00:00", false, "목", "00:00"), - OperatingTimeState("00:00", false, "금", "00:00"), - OperatingTimeState("00:00", false, "토", "00:00"), - OperatingTimeState("00:00", false, "일", "00:00"), + OperatingTimeState("00:00", false, "월", "00:00", "MONDAY"), + OperatingTimeState("00:00", false, "화", "00:00", "TUESDAY"), + OperatingTimeState("00:00", false, "수", "00:00", "WEDNESDAY"), + OperatingTimeState("00:00", false, "목", "00:00","THURSDAY"), + OperatingTimeState("00:00", false, "금", "00:00","FRIDAY"), + OperatingTimeState("00:00", false, "토", "00:00","SATURDAY"), + OperatingTimeState("00:00", false, "일", "00:00","SUNDAY"), ), val isDetailInfoValid: Boolean = false, val showDialog: Boolean = false, diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenViewmodel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenViewmodel.kt index df94610a1..8630153d7 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenViewmodel.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/InsertDetailInfoScreenViewmodel.kt @@ -33,7 +33,7 @@ class InsertDetailInfoScreenViewModel @Inject constructor( storeCategory = storeBasicInfo.storeCategory, storeName = storeBasicInfo.storeName, storeAddress = storeBasicInfo.storeAddress, - storeImage = storeBasicInfo.storeImage + storeImage = storeBasicInfo.storeImageFileUrl, ) } } @@ -172,7 +172,6 @@ class InsertDetailInfoScreenViewModel @Inject constructor( val storeDetailInfo = state postSideEffect(InsertDetailInfoScreenSideEffect.NavigateToCheckScreen(storeDetailInfo)) - Log.e("로그", storeDetailInfo.toString()) return@intent } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/operatingTime/OperationgTimeState.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/operatingTime/OperationgTimeState.kt index 8157277e8..e351a475f 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/operatingTime/OperationgTimeState.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertdetailinfo/operatingTime/OperationgTimeState.kt @@ -8,5 +8,6 @@ data class OperatingTimeState( val closeTime: String = "", val closed: Boolean = false, val dayOfWeek: String = "", - val openTime: String = "" + val openTime: String = "", + val dayOfWeekEnglish: String = "" ): Parcelable \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt index 262ab6fea..760a24302 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt @@ -2,8 +2,12 @@ package `in`.koreatech.business.feature.insertstore.insertmaininfo import android.app.Activity import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.provider.MediaStore +import android.provider.OpenableColumns +import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.Image @@ -50,6 +54,7 @@ import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.domain.model.owner.insertstore.StoreBasicInfo import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect +import java.io.InputStream @Composable @@ -72,6 +77,10 @@ fun InsertBasicInfoScreen( onStoreNameChange = { viewModel.insertStoreName(it) }, + onUploadImage = { + viewModel.getPreSignedUrl(it.first.first, it.first.second, it.second.first, it.second.second) + }, + onStoreAddressChange = { viewModel.insertStoreAddress(it) }, @@ -93,17 +102,48 @@ fun InsertBasicInfoScreenImpl( storeAddress: String = "", isBasicInfoValid: Boolean = false, onStoreImageChange: (Uri) -> Unit = {}, + onUploadImage:(Pair, Pair>) -> Unit = {}, onStoreNameChange: (String) -> Unit = {}, onStoreAddressChange: (String) -> Unit = {}, onNextButtonClicked: () -> Unit = {}, onBackPressed: () -> Unit = {} ) { + val context = LocalContext.current + val galleryLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { + + val inputStream = context.contentResolver.openInputStream(it) + + if (it.scheme.equals("content")) { + val cursor = context.contentResolver.query(it, null, null, null, null) + cursor.use { + if (cursor != null && cursor.moveToFirst()) { + val fileNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + val fileSizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) + + if (fileNameIndex != -1 && fileSizeIndex != -1) { + val fileName = cursor.getString(fileNameIndex) + val fileSize = cursor.getLong(fileSizeIndex) + + if (inputStream != null) { + onUploadImage( + Pair( + Pair(fileSize, "image/" + fileName.split(".")[1]), + Pair(fileName, BitmapFactory.decodeStream(inputStream)) + ) + ) + } + inputStream?.close() + } + } + } + } + onStoreImageChange(it) } } @@ -163,7 +203,7 @@ fun InsertBasicInfoScreenImpl( modifier = Modifier .padding(horizontal = 71.dp, vertical = 53.dp), painter = painterResource(R.drawable.ic_no_store_image), - contentDescription = "enptyStoreImage" + contentDescription = "emptyStoreImage" ) } else{ @@ -226,6 +266,7 @@ private fun HandleSideEffects(viewModel: InsertBasicInfoScreenViewModel, navigat BasicInfoErrorType.NullStoreName -> context.getString(R.string.insert_store_null_store_name) BasicInfoErrorType.NullStoreAddress -> context.getString(R.string.insert_store_null_store_address) BasicInfoErrorType.NullStoreImage -> context.getString(R.string.insert_store_null_store_image) + BasicInfoErrorType.FailUploadImage -> context.getString(R.string.insert_store_fail_upload_store_image) } ToastUtil.getInstance().makeShort(message) } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenSideEffect.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenSideEffect.kt index 6a19873ca..89ee185e7 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenSideEffect.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenSideEffect.kt @@ -1,8 +1,4 @@ package `in`.koreatech.business.feature.insertstore.insertmaininfo - -import `in`.koreatech.koin.domain.model.owner.insertstore.StoreBasicInfo - - sealed class InsertBasicInfoScreenSideEffect { data class ShowMessage(val type: BasicInfoErrorType): InsertBasicInfoScreenSideEffect() @@ -12,5 +8,6 @@ sealed class InsertBasicInfoScreenSideEffect { enum class BasicInfoErrorType { NullStoreName, NullStoreAddress, - NullStoreImage + NullStoreImage, + FailUploadImage } \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenState.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenState.kt index f3eda5515..4fe981535 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenState.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenState.kt @@ -9,6 +9,8 @@ data class InsertBasicInfoScreenState( val storeName: String = "", val storeAddress: String = "", val storeImage: Uri = Uri.EMPTY, + val storeImagePreSignedUrl: String = "", + val storeImageFileUrl: String = "", val storeImageIsEmpty: Boolean = true, val storeCategory: Int = 0, val isBasicInfoValid: Boolean = false diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt index 088271b79..4d12bcdf5 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt @@ -1,22 +1,37 @@ package `in`.koreatech.business.feature.insertstore.insertmaininfo +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.domain.model.owner.insertstore.StoreBasicInfo +import `in`.koreatech.koin.domain.model.store.StoreUrl +import `in`.koreatech.koin.domain.usecase.business.UploadFileUseCase +import `in`.koreatech.koin.domain.usecase.owner.AttachStoreFileUseCase +import `in`.koreatech.koin.domain.usecase.presignedurl.GetMarketPreSignedUrlUseCase +import `in`.koreatech.koin.domain.usecase.presignedurl.UploadPreSignedUrlUseCase +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import java.io.ByteArrayOutputStream +import java.io.InputStream import javax.inject.Inject @HiltViewModel class InsertBasicInfoScreenViewModel @Inject constructor( - savedStateHandle: SavedStateHandle + savedStateHandle: SavedStateHandle, + private val getMarketPreSignedUrlUseCase: GetMarketPreSignedUrlUseCase, + private val uploadFilesUseCase: UploadFileUseCase, + private val uploadPreSignedUrlUseCase : UploadPreSignedUrlUseCase ): ViewModel(), ContainerHost { override val container: Container = container(InsertBasicInfoScreenState(), savedStateHandle = savedStateHandle){ @@ -47,13 +62,84 @@ class InsertBasicInfoScreenViewModel @Inject constructor( storeImageIsEmpty() } + fun getPreSignedUrl( + fileSize: Long, + fileType: String, + fileName: String, + bitmap: Bitmap + ) { + intent { + if(state.storeImagePreSignedUrl == ""){ + viewModelScope.launch { + getMarketPreSignedUrlUseCase( + fileSize, fileType, fileName + ).onSuccess { + uploadImage( + preSignedUrl = it.second, + fileUrl = it.first, + bitmap = bitmap, + mediaType = fileType, + mediaSize = fileSize + ) + }.onFailure { + failUploadImage() + } + } + } + else{ + uploadImage( + preSignedUrl = state.storeImagePreSignedUrl, + fileUrl = state.storeImageFileUrl, + bitmap = bitmap, + mediaType = fileType, + mediaSize = fileSize + ) + } + } + + } + + private fun uploadImage( + preSignedUrl: String, + fileUrl: String, + bitmap: Bitmap, + mediaType: String, + mediaSize: Long + ) { + val byteArrayOutputStream = ByteArrayOutputStream() + + when(mediaType){ + "image/jpeg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + "image/jpg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + "image/png" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) + "image/webp" -> bitmap.compress(Bitmap.CompressFormat.WEBP, 100, byteArrayOutputStream) + "image/bmp" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) + } + + val bitmapByteArray = byteArrayOutputStream.toByteArray() + + viewModelScope.launch { + uploadFilesUseCase( + preSignedUrl, + bitmapByteArray, + mediaType, + mediaSize + ).onSuccess { + insertStorePreSignedUrl(preSignedUrl) + insertStoreFileUrl(fileUrl) + }.onFailure { + failUploadImage() + } + } + } + fun onNextButtonClick(){ intent{ if(state.isBasicInfoValid){ val storeBasicInfo = InsertBasicInfoScreenState( storeName = state.storeName, storeAddress = state.storeAddress, - storeImage = state.storeImage, + storeImageFileUrl = state.storeImageFileUrl, storeCategory = state.storeCategory ) postSideEffect(InsertBasicInfoScreenSideEffect.NavigateToInsertDetailInfoScreen(storeBasicInfo)) @@ -68,6 +154,10 @@ class InsertBasicInfoScreenViewModel @Inject constructor( } } + private fun failUploadImage() = intent{ + postSideEffect(InsertBasicInfoScreenSideEffect.ShowMessage(BasicInfoErrorType.FailUploadImage)) + } + private fun storeImageIsEmpty() = intent{ reduce { state.copy(storeImageIsEmpty = state.storeImage == Uri.EMPTY) @@ -85,6 +175,26 @@ class InsertBasicInfoScreenViewModel @Inject constructor( } } + private fun insertStorePreSignedUrl(url: String){ + intent{ + reduce { + state.copy( + storeImagePreSignedUrl = url + ) + } + } + } + + private fun insertStoreFileUrl(url: String){ + intent{ + reduce { + state.copy( + storeImageFileUrl = url + ) + } + } + } + private fun isBasicInfoValidate() = intent { reduce { state.copy( diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRoute.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRoute.kt index a178aaf06..45e65cd8b 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRoute.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRoute.kt @@ -5,5 +5,7 @@ enum class InsertStoreRoute(){ SELECT_CATEGORY, BASIC_INFO, DETAIL_INFO, - OPERATING_TIME + OPERATING_TIME, + CHECK_SCREEN, + FINISH_SCREEN } \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRouteNavigator.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRouteNavigator.kt index c67ab3337..2a64ff184 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRouteNavigator.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/navigator/InsertStoreRouteNavigator.kt @@ -14,7 +14,10 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import `in`.koreatech.business.feature.insertstore.finalcheckstore.FinalCheckStoreScreen +import `in`.koreatech.business.feature.insertstore.finalcheckstore.FinalCheckStoreScreenImpl import `in`.koreatech.business.feature.insertstore.insertdetailinfo.InsertDetailInfoScreen +import `in`.koreatech.business.feature.insertstore.insertdetailinfo.InsertDetailInfoScreenState import `in`.koreatech.business.feature.insertstore.insertdetailinfo.InsertDetailInfoScreenViewModel import `in`.koreatech.business.feature.insertstore.insertdetailinfo.operatingTime.OperatingTimeSettingScreen import `in`.koreatech.business.feature.insertstore.insertmaininfo.InsertBasicInfoScreen @@ -103,8 +106,8 @@ fun InsertStoreNavigator( viewModel = detailInfoScreenViewModel, - navigateToCheckScreen = { - + navigateToCheckScreen = { storeInfo -> + navigateToCheckScreen(navController, storeInfo) } ) } @@ -120,6 +123,17 @@ fun InsertStoreNavigator( viewModel = detailInfoScreenViewModel ) } + + composable( + route = InsertStoreRoute.CHECK_SCREEN.name + ){ + FinalCheckStoreScreen( + onBackPressed = { + navController.navigateUp() + }, + navigateToFinishScreen = {} + ) + } } } @@ -139,6 +153,15 @@ private fun navigateToDetailInfo( bundle.putParcelable("storeBasicInfo", storeBasicInfo) navController.navigate(InsertStoreRoute.DETAIL_INFO.name, args = bundle) } + +private fun navigateToCheckScreen( + navController: NavController, + storeInfo: InsertDetailInfoScreenState +) { + val bundle = Bundle() + bundle.putParcelable("storeInfo", storeInfo) + navController.navigate(InsertStoreRoute.CHECK_SCREEN.name, args = bundle) +} fun NavController.navigate( route: String, args: Bundle, diff --git a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt index d9e0276b7..8bcce122f 100644 --- a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt @@ -362,7 +362,7 @@ fun BusinessAuthScreen( businessAuthState.fileInfo.forEach { businessAuthViewModel.uploadImage( it.preSignedUrl, - businessAuthState.bitmap[businessAuthState.fileInfo.indexOf(it)].toString(), + businessAuthState.bitmap[businessAuthState.fileInfo.indexOf(it)], it.mediaType, it.fileSize, ) diff --git a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt index 4b5638605..24287fc92 100644 --- a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt +++ b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt @@ -19,6 +19,7 @@ import org.orbitmvi.orbit.syntax.simple.intent import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +import java.io.ByteArrayOutputStream import javax.inject.Inject @HiltViewModel @@ -127,12 +128,24 @@ class BusinessAuthViewModel @Inject constructor( fun uploadImage( url: String, - bitmap: String, + bitmap: Bitmap, mediaType: String, mediaSize: Long ) { + val byteArrayOutputStream = ByteArrayOutputStream() + + when(mediaType){ + "image/jpeg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + "image/jpg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + "image/png" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) + "image/webp" -> bitmap.compress(Bitmap.CompressFormat.WEBP, 100, byteArrayOutputStream) + "image/bmp" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) + } + + val bitmapByteArray = byteArrayOutputStream.toByteArray() + viewModelScope.launch(Dispatchers.IO) { - uploadFilesUseCase(url, bitmap, mediaType, mediaSize).onSuccess { + uploadFilesUseCase(url, bitmapByteArray, mediaType, mediaSize).onSuccess { intent { reduce { state.copy(error = null) } } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index aaa203e49..e0bd3968d 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -15,7 +15,8 @@ 2 / 4 3 / 4 4 / 4 - 전화번호 + 카테고리 + 전화번호 배달금액 운영시간 기타정보 @@ -111,8 +112,10 @@ 가게 이름을 입력해 주세요. 주소 정보를 입력해 주세요. 가게 사진을 등록해 주세요. + 이미지를 등록하는데 실패하였습니다. 3. 세부 정보 입력 + 등록 하시려는 업체의\n세부 정보를 입력해 주세요. 시간설정 평일/주말 운영시간 %1$s: %2$s ~ %3$s @@ -120,4 +123,7 @@ 가게 전화번호를 입력해 주세요. 가게 배달비를 입력해 주세요. 가게 기타 정보를 입력해 주세요. + + 4. 가게 정보 확인 + 입력하신 정보가 맞습니까? diff --git a/data/src/main/java/in/koreatech/koin/data/api/OwnerApi.kt b/data/src/main/java/in/koreatech/koin/data/api/OwnerApi.kt index 82c7e28c0..4334c7e22 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/OwnerApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/OwnerApi.kt @@ -9,6 +9,7 @@ import `in`.koreatech.koin.data.request.owner.VerificationCodeSmsRequest import `in`.koreatech.koin.data.request.owner.VerificationSmsRequest import `in`.koreatech.koin.data.response.owner.OwnerResponse import `in`.koreatech.koin.data.response.owner.OwnerVerificationCodeResponse +import `in`.koreatech.koin.data.response.store.StoreRegisterResponse import retrofit2.Response import retrofit2.http.Body import retrofit2.http.POST @@ -37,4 +38,7 @@ interface OwnerApi { @POST(URLConstant.OWNER.CODE_SMS) suspend fun postVerificationCodeSms(@Body ownerVerificationCode: VerificationCodeSmsRequest): OwnerVerificationCodeResponse + + @POST(URLConstant.OWNER.SHOPS) + suspend fun putMyStore(@Body storeRegisterResponse: StoreRegisterResponse): StoreRegisterResponse } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/api/UploadUrlApi.kt b/data/src/main/java/in/koreatech/koin/data/api/UploadUrlApi.kt index f0d40f9ba..e506b202f 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/UploadUrlApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/UploadUrlApi.kt @@ -9,4 +9,7 @@ import retrofit2.http.POST interface UploadUrlApi { @POST(URLConstant.UPLOAD.OWNERURL) suspend fun postUploadUrl(@Body uploadUrlRequest: UploadUrlRequest): UploadUrlResponse + + @POST(URLConstant.UPLOAD.MARKETURL) + suspend fun postUploadMarketUrl(@Body uploadUrlRequest: UploadUrlRequest): UploadUrlResponse } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/api/auth/OwnerAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/OwnerAuthApi.kt new file mode 100644 index 000000000..b05fe79de --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/OwnerAuthApi.kt @@ -0,0 +1,11 @@ +package `in`.koreatech.koin.data.api.auth + +import `in`.koreatech.koin.data.constant.URLConstant +import `in`.koreatech.koin.data.response.store.StoreRegisterResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface OwnerAuthApi{ + @POST(URLConstant.OWNER.SHOPS) + suspend fun postMyStore(@Body storeRegisterResponse: StoreRegisterResponse): StoreRegisterResponse +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/constant/URLConstant.kt b/data/src/main/java/in/koreatech/koin/data/constant/URLConstant.kt index 628de3080..88a53d71b 100644 --- a/data/src/main/java/in/koreatech/koin/data/constant/URLConstant.kt +++ b/data/src/main/java/in/koreatech/koin/data/constant/URLConstant.kt @@ -74,6 +74,7 @@ object URLConstant { const val CODE_SMS = "$OWNERS/$VERIFICATION/code/sms" const val SMS = "$OWNERS/$VERIFICATION/sms" const val PW = "password" + const val SHOPS = "$OWNER/shops" } object CALLVANS { @@ -132,5 +133,6 @@ object URLConstant { object UPLOAD { const val url = "/{domain}/upload/url" const val OWNERURL = "/owners/upload/url" + const val MARKETURL = "/market/upload/url" } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt index 5090bd351..3fe4a931a 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/source/RemoteDataSourceModule.kt @@ -5,6 +5,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import `in`.koreatech.koin.data.api.* +import `in`.koreatech.koin.data.api.auth.OwnerAuthApi import `in`.koreatech.koin.data.api.auth.UserAuthApi import `in`.koreatech.koin.data.source.remote.* import javax.inject.Singleton @@ -33,8 +34,9 @@ object RemoteDataSourceModule { @Singleton fun provideOwnerRemoteDataSource( ownerApi: OwnerApi, + ownerAuthApi: OwnerAuthApi ): OwnerRemoteDataSource { - return OwnerRemoteDataSource(ownerApi) + return OwnerRemoteDataSource(ownerApi, ownerAuthApi) } @Provides diff --git a/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt b/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt index 9fd96c7a0..3f1abcc29 100644 --- a/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt +++ b/data/src/main/java/in/koreatech/koin/data/mapper/StoreMapper.kt @@ -4,12 +4,14 @@ import com.google.gson.annotations.SerializedName import `in`.koreatech.koin.data.response.store.ShopMenuOptionsResponse import `in`.koreatech.koin.data.response.store.ShopMenusResponse import `in`.koreatech.koin.data.response.store.StoreCategoriesItemResponse +import `in`.koreatech.koin.data.response.store.StoreDayOffResponse import `in`.koreatech.koin.data.response.store.StoreDetailEventResponse import `in`.koreatech.koin.data.response.store.StoreEventItemReponse import `in`.koreatech.koin.data.response.store.StoreItemResponse import `in`.koreatech.koin.data.response.store.StoreItemWithMenusResponse import `in`.koreatech.koin.data.response.store.StoreMenuCategoriesResponse import `in`.koreatech.koin.data.response.store.StoreMenuResponse +import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime import `in`.koreatech.koin.domain.model.store.ShopEvent import `in`.koreatech.koin.domain.model.store.ShopEvents import `in`.koreatech.koin.domain.model.store.ShopMenus @@ -125,4 +127,29 @@ fun StoreDetailEventResponse.StoreEventDTO.toStoreDetailEvent() = ShopEvent( thumbnailImages = thumbnailImages ?: emptyList(), startDate = startDate ?: "", endDate = endDate ?: "" -) \ No newline at end of file +) + +fun List.toMyStoreDayOffResponse(): ArrayList { + val responseList = ArrayList() + for (dayOff in this) { + val response = StoreDayOffResponse(dayOff.closeTime, dayOff.closed, dayOff.dayOfWeek, dayOff.openTime) + responseList.add(response) + } + return responseList +} + + +fun String.toStringArray(): ArrayList { + val responseList = ArrayList() + responseList.add(this) + return responseList +} + +fun Int.toCategory(): List{ + val responseList = ArrayList() + + responseList.add(1) + responseList.add(this) + + return responseList +} \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/OwnerRegisterRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/OwnerRegisterRepositoryImpl.kt index b1b324fd8..9665b9024 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/OwnerRegisterRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/OwnerRegisterRepositoryImpl.kt @@ -1,9 +1,15 @@ package `in`.koreatech.koin.data.repository +import `in`.koreatech.koin.data.mapper.toCategory import `in`.koreatech.koin.data.mapper.toFileUrlList +import `in`.koreatech.koin.data.mapper.toMyStoreDayOffResponse +import `in`.koreatech.koin.data.mapper.toPhoneNumber +import `in`.koreatech.koin.data.mapper.toStringArray import `in`.koreatech.koin.data.request.owner.OwnerRegisterRequest +import `in`.koreatech.koin.data.response.store.StoreRegisterResponse import `in`.koreatech.koin.data.source.remote.OwnerRemoteDataSource import `in`.koreatech.koin.domain.model.owner.OwnerRegisterUrl +import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime import `in`.koreatech.koin.domain.repository.OwnerRegisterRepository import retrofit2.HttpException import java.io.EOFException @@ -37,4 +43,43 @@ class OwnerRegisterRepositoryImpl( Result.failure(t) } } + + override suspend fun storeRegister( + name: String, + category: Int, + address: String, + imageUri: String, + phoneNumber: String, + deliveryPrice: String, + description: String, + operatingTime: List, + isDeliveryOk: Boolean, + isCardOk: Boolean, + isBankOk: Boolean + ): Result { + return try { + ownerRemoteDataSource.postStoreRegister( + StoreRegisterResponse( + address = address, + categoryIds = category.toCategory(), + delivery = isDeliveryOk, + delivery_price = deliveryPrice.toInt(), + description = description, + imageUrls = imageUri.toStringArray(), + name = name, + open = operatingTime.toMyStoreDayOffResponse(), + payBank = isBankOk, + payCard = isCardOk, + phone = phoneNumber?.toPhoneNumber() ?: "" + ) + ) + Result.success(Unit) + } catch (e: EOFException) { + Result.success(Unit) + } catch (e: HttpException) { + Result.failure(e) + } catch (t: Throwable) { + Result.failure(t) + } + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt index 9502b24d5..8fbe70d67 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.data.repository +import android.util.Log import `in`.koreatech.koin.data.mapper.safeApiCall import `in`.koreatech.koin.data.requestbody.S3RequestBody import `in`.koreatech.koin.data.source.remote.PreSignedUrlRemoteDataSource @@ -34,11 +35,12 @@ class PreSignedUrlRepositoryImpl @Inject constructor( override suspend fun uploadFile( url: String, - bitmap: String, + bitmap: ByteArray, mediaType: String, mediaSize: Long ): Result { return safeApiCall { + val file = bitmap.toRequestBody(mediaType.toMediaTypeOrNull()) preSignedUrlRemoteDataSource.putPreSignedUrl(url, file) } diff --git a/data/src/main/java/in/koreatech/koin/data/repository/UploadUrlRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/UploadUrlRepositoryImpl.kt index 9617730e9..b47e75e3e 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/UploadUrlRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/UploadUrlRepositoryImpl.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.data.repository +import android.util.Log import `in`.koreatech.koin.data.request.upload.UploadUrlRequest import `in`.koreatech.koin.data.source.remote.UploadUrlRemoteDataSource import `in`.koreatech.koin.domain.repository.UploadUrlRepository @@ -31,4 +32,26 @@ class UploadUrlRepositoryImpl @Inject constructor( } } + override suspend fun getUploadMarketUrlResult( + contentLength: Long, + contentType: String, + fileName: String + ): Result> { + return try { + + val dataSource = uploadUrlRemoteDataSource.postUploadMarketUrl( + UploadUrlRequest(contentLength, contentType, fileName) + ) + + val preSignedUrl = dataSource.preSignedUrl + val fileUrl = dataSource.fileUrl + + Result.success(Pair(fileUrl, preSignedUrl)) + } catch (e: HttpException) { + Result.failure(e) + } catch (t: Throwable) { + Result.failure(t) + } + } + } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/store/StoreDayOffResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/store/StoreDayOffResponse.kt new file mode 100644 index 000000000..c99badcf6 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/store/StoreDayOffResponse.kt @@ -0,0 +1,10 @@ +package `in`.koreatech.koin.data.response.store + +import com.google.gson.annotations.SerializedName + +data class StoreDayOffResponse( + @SerializedName("close_time") val closeTime: String?, + @SerializedName("closed") val closed: Boolean, + @SerializedName("day_of_week") val dayOfWeek: String, + @SerializedName("open_time") val openTime: String? +) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/response/store/StoreRegisterResponse.kt b/data/src/main/java/in/koreatech/koin/data/response/store/StoreRegisterResponse.kt new file mode 100644 index 000000000..ce96ff31d --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/response/store/StoreRegisterResponse.kt @@ -0,0 +1,17 @@ +package `in`.koreatech.koin.data.response.store + +import com.google.gson.annotations.SerializedName + +data class StoreRegisterResponse ( + @SerializedName("address") val address: String, + @SerializedName("category_ids") val categoryIds: List, + @SerializedName("delivery") val delivery: Boolean, // 배달가능 + @SerializedName("delivery_price") val delivery_price: Int, + @SerializedName("description") val description: String, + @SerializedName("image_urls") val imageUrls: List, // 이미지 + @SerializedName("name") val name: String, // 가게이름 + @SerializedName("open") val open: List?, // 가게 휴점 시간 + @SerializedName("pay_bank") val payBank: Boolean, // 계좌이체 가능 + @SerializedName("pay_card") val payCard: Boolean, // 카드결제 가능 + @SerializedName("phone") val phone: String // 전화번호 +) diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/OwnerRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/OwnerRemoteDataSource.kt index 5e15333b3..6ed005b3e 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/OwnerRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/OwnerRemoteDataSource.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.data.source.remote import `in`.koreatech.koin.data.api.OwnerApi +import `in`.koreatech.koin.data.api.auth.OwnerAuthApi import `in`.koreatech.koin.data.request.owner.OwnerChangePasswordRequest import `in`.koreatech.koin.data.request.owner.OwnerRegisterRequest import `in`.koreatech.koin.data.request.owner.OwnerVerificationCodeRequest @@ -9,8 +10,12 @@ import `in`.koreatech.koin.data.request.owner.VerificationCodeSmsRequest import `in`.koreatech.koin.data.request.owner.VerificationSmsRequest import `in`.koreatech.koin.data.response.owner.OwnerResponse import `in`.koreatech.koin.data.response.owner.OwnerVerificationCodeResponse +import `in`.koreatech.koin.data.response.store.StoreRegisterResponse -class OwnerRemoteDataSource(private val ownerApi: OwnerApi) { +class OwnerRemoteDataSource( + private val ownerApi: OwnerApi, + private val ownerAuthApi: OwnerAuthApi + ) { suspend fun postVerificationCode(ownerVerificationCode: OwnerVerificationCodeRequest): OwnerVerificationCodeResponse { return ownerApi.postVerificationCode(ownerVerificationCode) } @@ -42,4 +47,8 @@ class OwnerRemoteDataSource(private val ownerApi: OwnerApi) { suspend fun postVerificationCodeSms(ownerVerificationCode: VerificationCodeSmsRequest): OwnerVerificationCodeResponse { return ownerApi.postVerificationCodeSms(ownerVerificationCode) } + + suspend fun postStoreRegister(storeRegisterResponse: StoreRegisterResponse): StoreRegisterResponse { + return ownerAuthApi.postMyStore(storeRegisterResponse) + } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/UploadUrlRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/UploadUrlRemoteDataSource.kt index 3fc250189..1922b78f3 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/UploadUrlRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/UploadUrlRemoteDataSource.kt @@ -9,4 +9,6 @@ class UploadUrlRemoteDataSource @Inject constructor( private val uploadUrl: UploadUrlApi ) { suspend fun postUploadUrl(uploadUrlRequest: UploadUrlRequest): UploadUrlResponse = uploadUrl.postUploadUrl(uploadUrlRequest) + + suspend fun postUploadMarketUrl(uploadUrlRequest: UploadUrlRequest): UploadUrlResponse = uploadUrl.postUploadMarketUrl(uploadUrlRequest) } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/OwnerRegisterRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/OwnerRegisterRepository.kt index 342841e17..7204460df 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/OwnerRegisterRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/OwnerRegisterRepository.kt @@ -1,6 +1,7 @@ package `in`.koreatech.koin.domain.repository import `in`.koreatech.koin.domain.model.owner.OwnerRegisterUrl +import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime interface OwnerRegisterRepository { suspend fun ownerRegister( @@ -13,4 +14,18 @@ interface OwnerRegisterRepository { shopId: Int?, shopName: String ): Result + + suspend fun storeRegister( + name: String, + category: Int, + address: String, + imageUri: String, + phoneNumber: String, + deliveryPrice: String, //배달비 + description: String, + operatingTime: List, + isDeliveryOk: Boolean, //배달 가능 여부 + isCardOk: Boolean, //카드결제 여부 + isBankOk: Boolean //계좌이체 여부 + ): Result } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt index 334d30931..a5b955f59 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt @@ -13,7 +13,7 @@ interface PreSignedUrlRepository { suspend fun uploadFile( url: String, - bitmap: String, + bitmap: ByteArray, mediaType: String, mediaSize: Long ): Result diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/UploadUrlRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/UploadUrlRepository.kt index d090207de..64ce81c9f 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/UploadUrlRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/UploadUrlRepository.kt @@ -6,4 +6,10 @@ interface UploadUrlRepository { contentType: String, fileName: String ): Result> + + suspend fun getUploadMarketUrlResult( + contentLength: Long, + contentType: String, + fileName: String + ): Result> } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt index 6c978dcd6..933556f84 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt @@ -8,7 +8,7 @@ class UploadFileUseCase @Inject constructor( ) { suspend operator fun invoke( url: String, - bitmap: String, + bitmap: ByteArray, mediaType: String, mediaSize: Long ): Result { diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/business/store/RegisterStoreUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/store/RegisterStoreUseCase.kt new file mode 100644 index 000000000..ffe3f7b8f --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/store/RegisterStoreUseCase.kt @@ -0,0 +1,37 @@ +package `in`.koreatech.koin.domain.usecase.business.store + +import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime +import `in`.koreatech.koin.domain.repository.OwnerRegisterRepository +import javax.inject.Inject + +class RegisterStoreUseCase @Inject constructor( + private val ownerRegisterRepository: OwnerRegisterRepository +) { + suspend operator fun invoke( + name: String, + category: Int, + address: String, + imageUri: String, + phoneNumber: String, + deliveryPrice: String, //배달비 + description: String, + operatingTime: List, + isDeliveryOk: Boolean, //배달 가능 여부 + isCardOk: Boolean, //카드결제 여부 + isBankOk: Boolean //계좌이체 여부 + ): Result { + return ownerRegisterRepository.storeRegister( + name = name, + category = category, + address = address, + imageUri = imageUri, + phoneNumber = phoneNumber, + deliveryPrice = deliveryPrice, //배달비 + description = description, + operatingTime = operatingTime, + isDeliveryOk = isDeliveryOk, //배달 가능 여부 + isCardOk = isCardOk, //카드결제 여부 + isBankOk = isBankOk //계좌이체 여부 + ) + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt new file mode 100644 index 000000000..ae0ff1777 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.presignedurl + +import `in`.koreatech.koin.domain.repository.UploadUrlRepository +import javax.inject.Inject + +class GetMarketPreSignedUrlUseCase @Inject constructor( + private val uploadUrlRepository: UploadUrlRepository +) { + suspend operator fun invoke( + contentLength: Long, + contentType: String, + fileName: String + ): Result> { + return uploadUrlRepository.getUploadMarketUrlResult(contentLength, contentType, fileName) + } +} \ No newline at end of file From 641f71566a627aa9af32ae01954a0d8a09c06ba8 Mon Sep 17 00:00:00 2001 From: hsgo2430 Date: Thu, 27 Jun 2024 21:11:38 +0900 Subject: [PATCH 2/3] =?UTF-8?q?1=EC=B0=A8=20=EC=BD=94=EB=A9=98=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../finalcheckstore/FinalCheckStoreScreen.kt | 29 +++--- .../FinalCheckStoreScreenViewmodel.kt | 88 ++++++++++++------- .../insertmaininfo/InsertBasicInfoScreen.kt | 5 +- .../InsertBasicInfoScreenViewModel.kt | 46 ++++------ .../koreatech/koin/core/upload/ImageUtil.kt | 32 +++++++ 5 files changed, 120 insertions(+), 80 deletions(-) create mode 100644 core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt index c57d8e74a..5f81830b7 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreen.kt @@ -124,40 +124,40 @@ fun FinalCheckStoreScreenImpl( item { NameTextField( - stringResource(id = R.string.category), - state.storeCategory.toString(), + textString = stringResource(id = R.string.category), + outputString = state.storeCategory.toString(), paddingTopValue = 32.dp ) } item { NameTextField( - stringResource(id = R.string.insert_store_store_name), - state.storeName, + textString = stringResource(id = R.string.insert_store_store_name), + outputString = state.storeName, paddingTopValue = 24.dp ) } item { NameTextField( - stringResource(id = R.string.insert_store_store_address), - state.storeAddress, + textString = stringResource(id = R.string.insert_store_store_address), + outputString = state.storeAddress, paddingTopValue = 24.dp ) } item { NameTextField( - stringResource(id = R.string.calling_number), - state.storePhoneNumber, + textString = stringResource(id = R.string.calling_number), + outputString = state.storePhoneNumber, paddingTopValue = 24.dp ) } item { NameTextField( - stringResource(id = R.string.delivery_fee), - state.storeDeliveryFee, + textString = stringResource(id = R.string.delivery_fee), + outputString = state.storeDeliveryFee, paddingTopValue = 24.dp ) } @@ -200,8 +200,8 @@ fun FinalCheckStoreScreenImpl( item { NameTextField( - stringResource(id = R.string.other_info), - state.storeOtherInfo, + textString = stringResource(id = R.string.other_info), + outputString = state.storeOtherInfo, paddingTopValue = 24.dp ) } @@ -256,12 +256,13 @@ fun FinalCheckStoreScreenImpl( @Composable fun NameTextField( + modifier: Modifier = Modifier, textString: String = "", outputString: String = "", paddingTopValue: Dp = 10.dp ){ Row( - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(horizontal = 32.dp) .padding(top = paddingTopValue), @@ -280,8 +281,6 @@ fun NameTextField( fontSize = 14.sp, fontWeight = FontWeight.Bold ) - - } } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt index 5df7ca031..4bf14a9f9 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/finalcheckstore/FinalCheckStoreScreenViewmodel.kt @@ -10,6 +10,8 @@ import `in`.koreatech.business.feature.insertstore.insertdetailinfo.InsertDetail import `in`.koreatech.business.feature.insertstore.insertdetailinfo.operatingTime.OperatingTimeState import `in`.koreatech.koin.domain.model.owner.insertstore.OperatingTime import `in`.koreatech.koin.domain.usecase.business.store.RegisterStoreUseCase +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost @@ -32,8 +34,20 @@ class FinalCheckStoreScreenViewModel @Inject constructor( if (storeInfoJson != null) getStoreInfo(storeInfoJson) } - private fun getStoreInfo(storeInfo: InsertDetailInfoScreenState){ - intent{ + private val registerStoreFlow = MutableSharedFlow() + + init { + viewModelScope.launch { + registerStoreFlow + .debounce(500L) + .collect { + performRegisterStore() + } + } + } + + private fun getStoreInfo(storeInfo: InsertDetailInfoScreenState) { + intent { reduce { state.copy( storeCategory = storeInfo.storeCategory, @@ -52,42 +66,48 @@ class FinalCheckStoreScreenViewModel @Inject constructor( } } - fun registerStore(){ + private suspend fun performRegisterStore(){ intent { - viewModelScope.launch { - registerStoreUseCase( - name = state.storeName, - category = state.storeCategory, - address = state.storeAddress, - imageUri = state.storeImage, - phoneNumber = state.storePhoneNumber, - deliveryPrice = state.storeDeliveryFee, - description = state.storeOtherInfo, - operatingTime = state.operatingTimeList.toOperatingTimeList(), - isDeliveryOk = state.isDeliveryOk, - isCardOk = state.isCardOk, - isBankOk = state.isBankOk - ).onSuccess { - intent{ - postSideEffect(FinalCheckStoreScreenSideEffect.GoToFinishScreen) - } - }.onFailure { - intent{ - postSideEffect(FinalCheckStoreScreenSideEffect.FailRegisterStore) - } + registerStoreUseCase( + name = state.storeName, + category = state.storeCategory, + address = state.storeAddress, + imageUri = state.storeImage, + phoneNumber = state.storePhoneNumber, + deliveryPrice = state.storeDeliveryFee, + description = state.storeOtherInfo, + operatingTime = state.operatingTimeList.toOperatingTimeList(), + isDeliveryOk = state.isDeliveryOk, + isCardOk = state.isCardOk, + isBankOk = state.isBankOk + ).onSuccess { + intent{ + postSideEffect(FinalCheckStoreScreenSideEffect.GoToFinishScreen) + } + }.onFailure { + intent{ + postSideEffect(FinalCheckStoreScreenSideEffect.FailRegisterStore) } } } } -} -private fun List.toOperatingTimeList(): List { - return this.map { operatingTimeState -> - OperatingTime( - closeTime = operatingTimeState.closeTime, - closed = operatingTimeState.closed, - dayOfWeek = operatingTimeState.dayOfWeekEnglish, - openTime = operatingTimeState.openTime - ) + fun registerStore() { + intent { + viewModelScope.launch { + registerStoreFlow.emit(state) + } + } + } + + private fun List.toOperatingTimeList(): List { + return this.map { operatingTimeState -> + OperatingTime( + closeTime = operatingTimeState.closeTime, + closed = operatingTimeState.closed, + dayOfWeek = operatingTimeState.dayOfWeekEnglish, + openTime = operatingTimeState.openTime + ) + } } -} +} \ No newline at end of file diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt index 760a24302..32f4962e0 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt @@ -51,6 +51,7 @@ import `in`.koreatech.business.ui.theme.ColorMinor import `in`.koreatech.business.ui.theme.ColorPrimary import `in`.koreatech.koin.core.R import `in`.koreatech.koin.core.toast.ToastUtil +import `in`.koreatech.koin.core.upload.toResizeBitmap import `in`.koreatech.koin.domain.model.owner.insertstore.StoreBasicInfo import org.orbitmvi.orbit.compose.collectAsState import org.orbitmvi.orbit.compose.collectSideEffect @@ -102,7 +103,7 @@ fun InsertBasicInfoScreenImpl( storeAddress: String = "", isBasicInfoValid: Boolean = false, onStoreImageChange: (Uri) -> Unit = {}, - onUploadImage:(Pair, Pair>) -> Unit = {}, + onUploadImage:(Pair, Pair>) -> Unit = {}, onStoreNameChange: (String) -> Unit = {}, onStoreAddressChange: (String) -> Unit = {}, onNextButtonClicked: () -> Unit = {}, @@ -134,7 +135,7 @@ fun InsertBasicInfoScreenImpl( onUploadImage( Pair( Pair(fileSize, "image/" + fileName.split(".")[1]), - Pair(fileName, BitmapFactory.decodeStream(inputStream)) + Pair(fileName, inputStream.toResizeBitmap(fileSize)) ) ) } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt index 4d12bcdf5..e1c57a2b5 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt @@ -8,8 +8,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import `in`.koreatech.koin.domain.model.owner.insertstore.StoreBasicInfo -import `in`.koreatech.koin.domain.model.store.StoreUrl +import `in`.koreatech.koin.core.upload.toCompressJPEG import `in`.koreatech.koin.domain.usecase.business.UploadFileUseCase import `in`.koreatech.koin.domain.usecase.owner.AttachStoreFileUseCase import `in`.koreatech.koin.domain.usecase.presignedurl.GetMarketPreSignedUrlUseCase @@ -66,11 +65,11 @@ class InsertBasicInfoScreenViewModel @Inject constructor( fileSize: Long, fileType: String, fileName: String, - bitmap: Bitmap + bitmap: Bitmap? ) { intent { if(state.storeImagePreSignedUrl == ""){ - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { getMarketPreSignedUrlUseCase( fileSize, fileType, fileName ).onSuccess { @@ -96,39 +95,28 @@ class InsertBasicInfoScreenViewModel @Inject constructor( ) } } - } private fun uploadImage( preSignedUrl: String, fileUrl: String, - bitmap: Bitmap, + bitmap: Bitmap?, mediaType: String, mediaSize: Long ) { - val byteArrayOutputStream = ByteArrayOutputStream() - - when(mediaType){ - "image/jpeg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) - "image/jpg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) - "image/png" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) - "image/webp" -> bitmap.compress(Bitmap.CompressFormat.WEBP, 100, byteArrayOutputStream) - "image/bmp" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) - } - - val bitmapByteArray = byteArrayOutputStream.toByteArray() - - viewModelScope.launch { - uploadFilesUseCase( - preSignedUrl, - bitmapByteArray, - mediaType, - mediaSize - ).onSuccess { - insertStorePreSignedUrl(preSignedUrl) - insertStoreFileUrl(fileUrl) - }.onFailure { - failUploadImage() + if (bitmap != null) { + viewModelScope.launch(Dispatchers.IO) { + uploadFilesUseCase( + preSignedUrl, + bitmap.toCompressJPEG(), + mediaType, + mediaSize + ).onSuccess { + insertStorePreSignedUrl(preSignedUrl) + insertStoreFileUrl(fileUrl) + }.onFailure { + failUploadImage() + } } } } diff --git a/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt b/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt new file mode 100644 index 000000000..81687226d --- /dev/null +++ b/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt @@ -0,0 +1,32 @@ +package `in`.koreatech.koin.core.upload + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.media.ExifInterface +import android.util.Log +import java.io.ByteArrayOutputStream +import java.io.InputStream + + +fun InputStream.toResizeBitmap(fileSize: Long): Bitmap? { + + return if(fileSize >= 1000000) + { + val option = BitmapFactory.Options() + + option.apply { + inJustDecodeBounds = false + inSampleSize = 4 + } + + BitmapFactory.decodeStream(this, null, option) + } + else + BitmapFactory.decodeStream(this) +} +fun Bitmap.toCompressJPEG(): ByteArray { + val byteArrayOutputStream = ByteArrayOutputStream() + this.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) + + return byteArrayOutputStream.toByteArray() +} \ No newline at end of file From 44d41f2767cfd64a6e79f3e3881b45ccb98b121e Mon Sep 17 00:00:00 2001 From: hsgo2430 Date: Sat, 29 Jun 2024 23:41:13 +0900 Subject: [PATCH 3/3] =?UTF-8?q?2=EC=B0=A8=20=EC=BD=94=EB=A9=98=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../insertmaininfo/InsertBasicInfoScreen.kt | 14 ++--- .../InsertBasicInfoScreenViewModel.kt | 43 +++++++------- .../signup/businessauth/BusinessAuthScreen.kt | 2 +- .../businessauth/BusinessAuthViewModel.kt | 18 +----- .../koreatech/koin/core/upload/ImageUtil.kt | 8 +++ .../data/di/repository/RepositoryModule.kt | 5 +- .../koin/data/di/usecase/UseCaseModule.kt | 23 ++++++++ .../repository/PreSignedUrlRepositoryImpl.kt | 9 ++- .../local/UploadImageLocalDataSource.kt | 57 +++++++++++++++++++ .../repository/PreSignedUrlRepository.kt | 3 +- .../usecase/business/UploadFileUseCase.kt | 13 +++-- .../GetMarketPreSignedUrlUseCase.kt | 10 +++- 12 files changed, 147 insertions(+), 58 deletions(-) create mode 100644 data/src/main/java/in/koreatech/koin/data/source/local/UploadImageLocalDataSource.kt diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt index 32f4962e0..5a554e9b9 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreen.kt @@ -103,7 +103,7 @@ fun InsertBasicInfoScreenImpl( storeAddress: String = "", isBasicInfoValid: Boolean = false, onStoreImageChange: (Uri) -> Unit = {}, - onUploadImage:(Pair, Pair>) -> Unit = {}, + onUploadImage:(Pair, Pair>) -> Unit = {}, onStoreNameChange: (String) -> Unit = {}, onStoreAddressChange: (String) -> Unit = {}, onNextButtonClicked: () -> Unit = {}, @@ -116,12 +116,12 @@ fun InsertBasicInfoScreenImpl( contract = ActivityResultContracts.StartActivityForResult() ) { result -> if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { + result.data?.data?.let { uri -> - val inputStream = context.contentResolver.openInputStream(it) + val inputStream = context.contentResolver.openInputStream(uri) - if (it.scheme.equals("content")) { - val cursor = context.contentResolver.query(it, null, null, null, null) + if (uri.scheme.equals("content")) { + val cursor = context.contentResolver.query(uri, null, null, null, null) cursor.use { if (cursor != null && cursor.moveToFirst()) { val fileNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) @@ -135,7 +135,7 @@ fun InsertBasicInfoScreenImpl( onUploadImage( Pair( Pair(fileSize, "image/" + fileName.split(".")[1]), - Pair(fileName, inputStream.toResizeBitmap(fileSize)) + Pair(fileName, uri.toString()) ) ) } @@ -145,7 +145,7 @@ fun InsertBasicInfoScreenImpl( } } - onStoreImageChange(it) + onStoreImageChange(uri) } } } diff --git a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt index e1c57a2b5..5221b0f2e 100644 --- a/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt +++ b/business/src/main/java/in/koreatech/business/feature/insertstore/insertmaininfo/InsertBasicInfoScreenViewModel.kt @@ -29,8 +29,7 @@ import javax.inject.Inject class InsertBasicInfoScreenViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val getMarketPreSignedUrlUseCase: GetMarketPreSignedUrlUseCase, - private val uploadFilesUseCase: UploadFileUseCase, - private val uploadPreSignedUrlUseCase : UploadPreSignedUrlUseCase + private val uploadFilesUseCase: UploadFileUseCase ): ViewModel(), ContainerHost { override val container: Container = container(InsertBasicInfoScreenState(), savedStateHandle = savedStateHandle){ @@ -65,20 +64,20 @@ class InsertBasicInfoScreenViewModel @Inject constructor( fileSize: Long, fileType: String, fileName: String, - bitmap: Bitmap? + imageUri: String ) { intent { if(state.storeImagePreSignedUrl == ""){ - viewModelScope.launch(Dispatchers.IO) { + viewModelScope.launch{ getMarketPreSignedUrlUseCase( fileSize, fileType, fileName ).onSuccess { uploadImage( preSignedUrl = it.second, fileUrl = it.first, - bitmap = bitmap, mediaType = fileType, - mediaSize = fileSize + mediaSize = fileSize, + imageUri = imageUri ) }.onFailure { failUploadImage() @@ -89,9 +88,9 @@ class InsertBasicInfoScreenViewModel @Inject constructor( uploadImage( preSignedUrl = state.storeImagePreSignedUrl, fileUrl = state.storeImageFileUrl, - bitmap = bitmap, mediaType = fileType, - mediaSize = fileSize + mediaSize = fileSize, + imageUri = imageUri ) } } @@ -100,23 +99,21 @@ class InsertBasicInfoScreenViewModel @Inject constructor( private fun uploadImage( preSignedUrl: String, fileUrl: String, - bitmap: Bitmap?, mediaType: String, - mediaSize: Long + mediaSize: Long, + imageUri: String ) { - if (bitmap != null) { - viewModelScope.launch(Dispatchers.IO) { - uploadFilesUseCase( - preSignedUrl, - bitmap.toCompressJPEG(), - mediaType, - mediaSize - ).onSuccess { - insertStorePreSignedUrl(preSignedUrl) - insertStoreFileUrl(fileUrl) - }.onFailure { - failUploadImage() - } + viewModelScope.launch{ + uploadFilesUseCase( + preSignedUrl, + mediaType, + mediaSize, + imageUri + ).onSuccess { + insertStorePreSignedUrl(preSignedUrl) + insertStoreFileUrl(fileUrl) + }.onFailure { + failUploadImage() } } } diff --git a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt index 8bcce122f..8cecf290b 100644 --- a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt +++ b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthScreen.kt @@ -362,7 +362,7 @@ fun BusinessAuthScreen( businessAuthState.fileInfo.forEach { businessAuthViewModel.uploadImage( it.preSignedUrl, - businessAuthState.bitmap[businessAuthState.fileInfo.indexOf(it)], + it.uri, it.mediaType, it.fileSize, ) diff --git a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt index 24287fc92..4ca08beba 100644 --- a/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt +++ b/business/src/main/java/in/koreatech/business/feature/signup/businessauth/BusinessAuthViewModel.kt @@ -128,24 +128,12 @@ class BusinessAuthViewModel @Inject constructor( fun uploadImage( url: String, - bitmap: Bitmap, + imageUri: String, mediaType: String, mediaSize: Long ) { - val byteArrayOutputStream = ByteArrayOutputStream() - - when(mediaType){ - "image/jpeg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) - "image/jpg" -> bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) - "image/png" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) - "image/webp" -> bitmap.compress(Bitmap.CompressFormat.WEBP, 100, byteArrayOutputStream) - "image/bmp" -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) - } - - val bitmapByteArray = byteArrayOutputStream.toByteArray() - - viewModelScope.launch(Dispatchers.IO) { - uploadFilesUseCase(url, bitmapByteArray, mediaType, mediaSize).onSuccess { + viewModelScope.launch{ + uploadFilesUseCase(url, imageUri, mediaSize, mediaType).onSuccess { intent { reduce { state.copy(error = null) } } diff --git a/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt b/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt index 81687226d..2f198b469 100644 --- a/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt +++ b/core/src/main/java/in/koreatech/koin/core/upload/ImageUtil.kt @@ -2,6 +2,7 @@ package `in`.koreatech.koin.core.upload import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Matrix import android.media.ExifInterface import android.util.Log import java.io.ByteArrayOutputStream @@ -24,6 +25,13 @@ fun InputStream.toResizeBitmap(fileSize: Long): Bitmap? { else BitmapFactory.decodeStream(this) } + +fun Bitmap.rotateBitmap(angle: Float): Bitmap { + val matrix = Matrix() + matrix.postRotate(angle) + return Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true) +} + fun Bitmap.toCompressJPEG(): ByteArray { val byteArrayOutputStream = ByteArrayOutputStream() this.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream) diff --git a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt index 9ea5f1982..3a597806c 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/repository/RepositoryModule.kt @@ -142,9 +142,10 @@ object RepositoryModule { @Provides @Singleton fun providePreSignedUrlRepository( - preSignedUrlRemoteDataSource: PreSignedUrlRemoteDataSource + preSignedUrlRemoteDataSource: PreSignedUrlRemoteDataSource, + uploadImageLocalDataSource :UploadImageLocalDataSource ): PreSignedUrlRepository { - return PreSignedUrlRepositoryImpl(preSignedUrlRemoteDataSource) + return PreSignedUrlRepositoryImpl(preSignedUrlRemoteDataSource, uploadImageLocalDataSource) } @Provides diff --git a/data/src/main/java/in/koreatech/koin/data/di/usecase/UseCaseModule.kt b/data/src/main/java/in/koreatech/koin/data/di/usecase/UseCaseModule.kt index 8f88e4f8d..e7556e213 100644 --- a/data/src/main/java/in/koreatech/koin/data/di/usecase/UseCaseModule.kt +++ b/data/src/main/java/in/koreatech/koin/data/di/usecase/UseCaseModule.kt @@ -5,7 +5,12 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import `in`.koreatech.koin.core.qualifier.DefaultDispatcher +import `in`.koreatech.koin.core.qualifier.IoDispatcher +import `in`.koreatech.koin.domain.repository.PreSignedUrlRepository import `in`.koreatech.koin.domain.repository.StoreRepository +import `in`.koreatech.koin.domain.repository.UploadUrlRepository +import `in`.koreatech.koin.domain.usecase.business.UploadFileUseCase +import `in`.koreatech.koin.domain.usecase.presignedurl.GetMarketPreSignedUrlUseCase import `in`.koreatech.koin.domain.usecase.store.SearchStoreUseCase import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Singleton @@ -21,4 +26,22 @@ object UseCaseModule { ): SearchStoreUseCase { return SearchStoreUseCase(storeRepository, coroutineDispatcher) } + + @Provides + @Singleton + fun provideGetMarketPreSignedUrlUseCase( + uploadUrlRepository: UploadUrlRepository, + @IoDispatcher coroutineDispatcher: CoroutineDispatcher, + ): GetMarketPreSignedUrlUseCase { + return GetMarketPreSignedUrlUseCase(uploadUrlRepository, coroutineDispatcher) + } + + @Provides + @Singleton + fun provideUploadFileUseCase( + preSignedUrlRepository: PreSignedUrlRepository, + @IoDispatcher coroutineDispatcher: CoroutineDispatcher, + ): UploadFileUseCase { + return UploadFileUseCase(preSignedUrlRepository, coroutineDispatcher) + } } diff --git a/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt index 8fbe70d67..d542891aa 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/PreSignedUrlRepositoryImpl.kt @@ -1,8 +1,10 @@ package `in`.koreatech.koin.data.repository import android.util.Log +import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.data.mapper.safeApiCall import `in`.koreatech.koin.data.requestbody.S3RequestBody +import `in`.koreatech.koin.data.source.local.UploadImageLocalDataSource import `in`.koreatech.koin.data.source.remote.PreSignedUrlRemoteDataSource import `in`.koreatech.koin.domain.repository.PreSignedUrlRepository import okhttp3.MediaType.Companion.toMediaType @@ -13,7 +15,8 @@ import java.io.InputStream import javax.inject.Inject class PreSignedUrlRepositoryImpl @Inject constructor( - private val preSignedUrlRemoteDataSource: PreSignedUrlRemoteDataSource + private val preSignedUrlRemoteDataSource: PreSignedUrlRemoteDataSource, + private val uploadImageLocalDataSource : UploadImageLocalDataSource ) : PreSignedUrlRepository { override suspend fun putPreSignedUrl( url: String, @@ -35,13 +38,13 @@ class PreSignedUrlRepositoryImpl @Inject constructor( override suspend fun uploadFile( url: String, - bitmap: ByteArray, + imageUri: String, mediaType: String, mediaSize: Long ): Result { return safeApiCall { - val file = bitmap.toRequestBody(mediaType.toMediaTypeOrNull()) + val file = uploadImageLocalDataSource.uriToBitmap(imageUri, mediaSize).toRequestBody(mediaType.toMediaTypeOrNull()) preSignedUrlRemoteDataSource.putPreSignedUrl(url, file) } } diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/UploadImageLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/UploadImageLocalDataSource.kt new file mode 100644 index 000000000..474d6d203 --- /dev/null +++ b/data/src/main/java/in/koreatech/koin/data/source/local/UploadImageLocalDataSource.kt @@ -0,0 +1,57 @@ +package `in`.koreatech.koin.data.source.local + +import android.content.Context +import android.graphics.Bitmap +import android.media.ExifInterface +import android.net.Uri +import android.os.Build +import android.provider.OpenableColumns +import android.util.Log +import androidx.annotation.RequiresApi +import dagger.hilt.android.qualifiers.ApplicationContext +import `in`.koreatech.koin.core.upload.rotateBitmap +import `in`.koreatech.koin.core.upload.toCompressJPEG +import `in`.koreatech.koin.core.upload.toResizeBitmap +import javax.inject.Inject + +class UploadImageLocalDataSource @Inject constructor( + @ApplicationContext private val applicationContext: Context +) { + fun uriToBitmap(uriString: String, fileSize: Long): ByteArray{ + val uri = Uri.parse(uriString) + val bitmapInputStream = applicationContext.contentResolver.openInputStream(uri) + val exifInterfaceInputStream = applicationContext.contentResolver.openInputStream(uri) + lateinit var imageBitmap: Bitmap + + if (uri.scheme.equals("content")) { + val cursor = applicationContext.contentResolver.query(uri, null, null, null, null) + cursor.use { + if (cursor != null && cursor.moveToFirst()) { + + if (bitmapInputStream != null && exifInterfaceInputStream != null) { + val bitmap = bitmapInputStream.toResizeBitmap(fileSize) + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + val exif = ExifInterface(exifInterfaceInputStream) + val rotatedBitmap = when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) { + ExifInterface.ORIENTATION_ROTATE_90 -> bitmap?.rotateBitmap(90f) + ExifInterface.ORIENTATION_ROTATE_180 -> bitmap?.rotateBitmap(180f) + ExifInterface.ORIENTATION_ROTATE_270 -> bitmap?.rotateBitmap(270f) + else -> bitmap?.rotateBitmap(90f) + } + if(rotatedBitmap != null){ + imageBitmap = rotatedBitmap + } + } + else{ + if(bitmap != null) imageBitmap = bitmap + } + } + + bitmapInputStream?.close() + exifInterfaceInputStream?.close() + } + } + } + return imageBitmap.toCompressJPEG() + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt index a5b955f59..6198f2625 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/PreSignedUrlRepository.kt @@ -13,8 +13,9 @@ interface PreSignedUrlRepository { suspend fun uploadFile( url: String, - bitmap: ByteArray, + imageUri: String, mediaType: String, mediaSize: Long ): Result + } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt index 933556f84..ab04a82f4 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/business/UploadFileUseCase.kt @@ -1,17 +1,22 @@ package `in`.koreatech.koin.domain.usecase.business import `in`.koreatech.koin.domain.repository.PreSignedUrlRepository +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext import javax.inject.Inject class UploadFileUseCase @Inject constructor( - private val preSignedUrlRepository: PreSignedUrlRepository + private val preSignedUrlRepository: PreSignedUrlRepository, + private val coroutineDispatcher: CoroutineDispatcher ) { suspend operator fun invoke( url: String, - bitmap: ByteArray, mediaType: String, - mediaSize: Long + mediaSize: Long, + imageUri: String ): Result { - return preSignedUrlRepository.uploadFile(url, bitmap, mediaType, mediaSize) + return withContext(coroutineDispatcher){ + preSignedUrlRepository.uploadFile(url,imageUri, mediaType, mediaSize) + } } } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt index ae0ff1777..ab76c5bb8 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/presignedurl/GetMarketPreSignedUrlUseCase.kt @@ -1,16 +1,22 @@ package `in`.koreatech.koin.domain.usecase.presignedurl import `in`.koreatech.koin.domain.repository.UploadUrlRepository +import io.github.aakira.napier.Napier +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext import javax.inject.Inject class GetMarketPreSignedUrlUseCase @Inject constructor( - private val uploadUrlRepository: UploadUrlRepository + private val uploadUrlRepository: UploadUrlRepository, + private val coroutineDispatcher: CoroutineDispatcher ) { suspend operator fun invoke( contentLength: Long, contentType: String, fileName: String ): Result> { - return uploadUrlRepository.getUploadMarketUrlResult(contentLength, contentType, fileName) + return withContext(coroutineDispatcher){ + uploadUrlRepository.getUploadMarketUrlResult(contentLength, contentType, fileName) + } } } \ No newline at end of file