diff --git a/data/src/main/java/kr/genti/data/dto/request/GenerateRequestDto.kt b/data/src/main/java/kr/genti/data/dto/request/GenerateRequestDto.kt index 8204b2bc..30987c9f 100644 --- a/data/src/main/java/kr/genti/data/dto/request/GenerateRequestDto.kt +++ b/data/src/main/java/kr/genti/data/dto/request/GenerateRequestDto.kt @@ -4,20 +4,31 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kr.genti.domain.entity.request.GenerateRequestModel import kr.genti.domain.enums.CameraAngle +import kr.genti.domain.enums.PictureRatio import kr.genti.domain.enums.ShotCoverage @Serializable data class GenerateRequestDto( @SerialName("prompt") val prompt: String, - @SerialName("postPictureKey") - val postPictureKey: String, + @SerialName("posePictureKey") + val posePictureKey: String?, @SerialName("facePictureKeyList") val facePictureKeyList: List, @SerialName("cameraAngle") val cameraAngle: CameraAngle, @SerialName("shotCoverage") val shotCoverage: ShotCoverage, + @SerialName("pictureRatio") + val pictureRatio: PictureRatio, ) -fun GenerateRequestModel.toDto() = GenerateRequestDto(prompt, postPictureKey, facePictureKeyList, cameraAngle, shotCoverage) +fun GenerateRequestModel.toDto() = + GenerateRequestDto( + prompt, + posePictureKey, + facePictureKeyList, + cameraAngle, + shotCoverage, + pictureRatio, + ) diff --git a/domain/src/main/kotlin/kr/genti/domain/entity/request/GenerateRequestModel.kt b/domain/src/main/kotlin/kr/genti/domain/entity/request/GenerateRequestModel.kt index 4e24d8f3..9543456d 100644 --- a/domain/src/main/kotlin/kr/genti/domain/entity/request/GenerateRequestModel.kt +++ b/domain/src/main/kotlin/kr/genti/domain/entity/request/GenerateRequestModel.kt @@ -1,12 +1,14 @@ package kr.genti.domain.entity.request import kr.genti.domain.enums.CameraAngle +import kr.genti.domain.enums.PictureRatio import kr.genti.domain.enums.ShotCoverage data class GenerateRequestModel( val prompt: String, - val postPictureKey: String, + val posePictureKey: String?, val facePictureKeyList: List, val cameraAngle: CameraAngle, val shotCoverage: ShotCoverage, + val pictureRatio: PictureRatio, ) diff --git a/domain/src/main/kotlin/kr/genti/domain/enums/ImageRatio.kt b/domain/src/main/kotlin/kr/genti/domain/enums/PictureRatio.kt similarity index 51% rename from domain/src/main/kotlin/kr/genti/domain/enums/ImageRatio.kt rename to domain/src/main/kotlin/kr/genti/domain/enums/PictureRatio.kt index 966719a8..bec7aa3f 100644 --- a/domain/src/main/kotlin/kr/genti/domain/enums/ImageRatio.kt +++ b/domain/src/main/kotlin/kr/genti/domain/enums/PictureRatio.kt @@ -1,8 +1,8 @@ package kr.genti.domain.enums -enum class ImageRatio(private val description: String) { - THREE("3:2"), - TWO("2:3"), +enum class PictureRatio(private val description: String) { + THREE_TWO("3:2"), + TWO_THREE("2:3"), ; override fun toString(): String { diff --git a/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt b/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt index b62e9bf8..08e9eb29 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/MainActivity.kt @@ -26,7 +26,7 @@ class MainActivity : BaseActivity(R.layout.activity_main) { setStatusBarColor() } - private fun initBnvItemIconTintList() { + fun initBnvItemIconTintList() { with(binding.bnvMain) { itemIconTintList = null selectedItemId = R.id.menu_feed diff --git a/presentation/src/main/java/kr/genti/presentation/main/create/CreateFragment.kt b/presentation/src/main/java/kr/genti/presentation/main/create/CreateFragment.kt index 37d930b1..ab17f727 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/create/CreateFragment.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/create/CreateFragment.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.onEach import kr.genti.core.base.BaseFragment import kr.genti.core.extension.setOnSingleClickListener import kr.genti.core.extension.setStatusBarColor +import kr.genti.core.state.UiState import kr.genti.presentation.R import kr.genti.presentation.databinding.FragmentCreateBinding @@ -31,6 +32,7 @@ class CreateFragment() : BaseFragment(R.layout.fragment_c initView() initBackBtnListener() observeProgressBar() + observeGeneratingState() } private fun initView() { @@ -72,6 +74,18 @@ class CreateFragment() : BaseFragment(R.layout.fragment_c }.launchIn(lifecycleScope) } + private fun observeGeneratingState() { + viewModel.totalGeneratingState.flowWithLifecycle(lifecycle).onEach { state -> + if (state == UiState.Loading) { + setStatusBarColor(R.color.background_50) + binding.layoutLoading.isVisible = true + } else { + setStatusBarColor(R.color.background_white) + binding.layoutLoading.isVisible = false + } + }.launchIn(lifecycleScope) + } + companion object { const val PROPERTY_PROGRESS = "progress" } diff --git a/presentation/src/main/java/kr/genti/presentation/main/create/CreateViewModel.kt b/presentation/src/main/java/kr/genti/presentation/main/create/CreateViewModel.kt index afb0cd8f..eef78691 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/create/CreateViewModel.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/create/CreateViewModel.kt @@ -18,7 +18,7 @@ import kr.genti.domain.entity.response.S3PresignedUrlModel import kr.genti.domain.entity.response.emptyImageFileModel import kr.genti.domain.enums.CameraAngle import kr.genti.domain.enums.FileType -import kr.genti.domain.enums.ImageRatio +import kr.genti.domain.enums.PictureRatio import kr.genti.domain.enums.ShotCoverage import kr.genti.domain.repository.CreateRepository import kr.genti.domain.repository.UploadRepository @@ -36,7 +36,7 @@ class CreateViewModel var plusImage = emptyImageFileModel() val isWritten = MutableLiveData(false) - val selectedRatio = MutableLiveData() + val selectedRatio = MutableLiveData() val selectedAngle = MutableLiveData() val selectedCoverage = MutableLiveData() val isSelected = MutableLiveData(false) @@ -56,8 +56,8 @@ class CreateViewModel private val _getRandomPromptState = MutableStateFlow>(UiState.Empty) val getRandomPromptState: StateFlow> = _getRandomPromptState - private val _totalGeneratingResult = MutableSharedFlow() - val totalGeneratingResult: SharedFlow = _totalGeneratingResult + private val _totalGeneratingState = MutableStateFlow>(UiState.Empty) + val totalGeneratingState: StateFlow> = _totalGeneratingState private var uploadCheckList = mutableListOf(false, false, false, true) private var plusImageS3Key: String? = null @@ -75,7 +75,7 @@ class CreateViewModel isWritten.value = prompt.value?.isNotEmpty() } - fun selectRatio(item: ImageRatio) { + fun selectRatio(item: PictureRatio) { selectedRatio.value = item checkSelected() } @@ -123,6 +123,12 @@ class CreateViewModel } fun getS3PresignedUrls() { + _totalGeneratingState.value = UiState.Loading + getSingleS3Url() + getMultiUrls() + } + + private fun getSingleS3Url() { if (plusImage.id != (-1).toLong()) { uploadCheckList[3] = false viewModelScope.launch { @@ -136,10 +142,13 @@ class CreateViewModel plusImageS3Key = uriModel.s3Key postSingleImage(uriModel) }.onFailure { - _totalGeneratingResult.emit(false) + _totalGeneratingState.value = UiState.Failure(it.message.toString()) } } } + } + + private fun getMultiUrls() { viewModelScope.launch { createRepository.getS3MultiUrl( listOf( @@ -151,7 +160,7 @@ class CreateViewModel imageS3KeyList = uriList.map { it.s3Key } postMultiImage(uriList) }.onFailure { - _totalGeneratingResult.emit(false) + _totalGeneratingState.value = UiState.Failure(it.message.toString()) } } } @@ -164,7 +173,7 @@ class CreateViewModel uploadCheckList[3] = true checkAllUploadFinished() }.onFailure { - _totalGeneratingResult.emit(false) + _totalGeneratingState.value = UiState.Failure(it.message.toString()) } } } @@ -177,30 +186,31 @@ class CreateViewModel uploadCheckList[i] = true if (i == 2) checkAllUploadFinished() }.onFailure { - _totalGeneratingResult.emit(false) + _totalGeneratingState.value = UiState.Failure(it.message.toString()) return@launch } } } } - // TODO: request 수정 + // TODO: plusImageS3Key nullable로 수정 private fun checkAllUploadFinished() { if (uploadCheckList.all { it }) { viewModelScope.launch { createRepository.postToGenerate( GenerateRequestModel( prompt.value ?: return@launch, - plusImageS3Key ?: return@launch, + plusImageS3Key ?: "", imageS3KeyList ?: return@launch, selectedAngle.value ?: return@launch, selectedCoverage.value ?: return@launch, + selectedRatio.value ?: return@launch, ), ) .onSuccess { - _totalGeneratingResult.emit(true) + _totalGeneratingState.value = UiState.Success(it) }.onFailure { - _totalGeneratingResult.emit(false) + _totalGeneratingState.value = UiState.Failure(it.message.toString()) } } } diff --git a/presentation/src/main/java/kr/genti/presentation/main/create/SelfieFragment.kt b/presentation/src/main/java/kr/genti/presentation/main/create/SelfieFragment.kt index ff536d79..cc468865 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/create/SelfieFragment.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/create/SelfieFragment.kt @@ -1,5 +1,6 @@ package kr.genti.presentation.main.create +import android.app.Activity import android.content.Intent import android.os.Bundle import android.text.SpannableString @@ -25,16 +26,20 @@ import kr.genti.core.extension.getFileName import kr.genti.core.extension.setOnSingleClickListener import kr.genti.core.extension.stringOf import kr.genti.core.extension.toast +import kr.genti.core.state.UiState import kr.genti.domain.entity.response.ImageFileModel import kr.genti.presentation.R import kr.genti.presentation.databinding.FragmentSelfieBinding +import kr.genti.presentation.main.MainActivity +import kr.genti.presentation.main.feed.FeedFragment import kr.genti.presentation.result.waiting.WaitingActivity import kotlin.math.max @AndroidEntryPoint class SelfieFragment() : BaseFragment(R.layout.fragment_selfie) { private val viewModel by activityViewModels() - lateinit var activityResult: ActivityResultLauncher + private lateinit var photoPickerResult: ActivityResultLauncher + private lateinit var waitingResult: ActivityResultLauncher override fun onViewCreated( view: View, @@ -49,7 +54,8 @@ class SelfieFragment() : BaseFragment(R.layout.fragment_s setGalleryImage() setBulletPointList() setGuideListBlur() - observeGetS3UrlResult() + initWaitingResult() + observeGeneratingState() } override fun onResume() { @@ -78,10 +84,10 @@ class SelfieFragment() : BaseFragment(R.layout.fragment_s private fun initAddImageBtnListener() { with(binding) { btnSelfieAdd.setOnSingleClickListener { - activityResult.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + photoPickerResult.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } layoutAddedImage.setOnSingleClickListener { - activityResult.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + photoPickerResult.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } } } @@ -93,7 +99,7 @@ class SelfieFragment() : BaseFragment(R.layout.fragment_s } private fun setGalleryImage() { - activityResult = + photoPickerResult = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(3)) { uris -> if (uris.isNotEmpty()) { with(viewModel) { @@ -121,7 +127,8 @@ class SelfieFragment() : BaseFragment(R.layout.fragment_s val imageViews = with(binding) { listOf(ivAddedImage1, ivAddedImage2, ivAddedImage3) } imageViews.forEach { it.setImageDrawable(null) } - viewModel.imageList.take(3).forEachIndexed { index, imageFile -> imageViews[index].load(imageFile.url) } + viewModel.imageList.take(3) + .forEachIndexed { index, imageFile -> imageViews[index].load(imageFile.url) } binding.layoutAddedImage.isVisible = true } } @@ -161,14 +168,34 @@ class SelfieFragment() : BaseFragment(R.layout.fragment_s } } - private fun observeGetS3UrlResult() { - viewModel.totalGeneratingResult.flowWithLifecycle(lifecycle).onEach { result -> - if (!result) { - toast(stringOf(R.string.error_msg)) - } else { - Intent(requireActivity(), WaitingActivity::class.java).apply { - startActivity(this) + private fun initWaitingResult() { + if (!::waitingResult.isInitialized) { + waitingResult = + registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + requireActivity().supportFragmentManager.beginTransaction() + .replace(R.id.fcv_main, FeedFragment()) + .commit() + (requireActivity() as? MainActivity)?.initBnvItemIconTintList() + } + } + } + } + + private fun observeGeneratingState() { + viewModel.totalGeneratingState.flowWithLifecycle(lifecycle).onEach { state -> + when (state) { + is UiState.Success -> { + waitingResult.launch(Intent(requireContext(), WaitingActivity::class.java)) + } + + is UiState.Failure -> { + toast(stringOf(R.string.error_msg)) } + + else -> return@onEach } }.launchIn(lifecycleScope) } diff --git a/presentation/src/main/java/kr/genti/presentation/main/feed/FeedFragment.kt b/presentation/src/main/java/kr/genti/presentation/main/feed/FeedFragment.kt index 37553c93..9d3fdc2c 100644 --- a/presentation/src/main/java/kr/genti/presentation/main/feed/FeedFragment.kt +++ b/presentation/src/main/java/kr/genti/presentation/main/feed/FeedFragment.kt @@ -2,6 +2,7 @@ package kr.genti.presentation.main.feed import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -79,6 +80,13 @@ class FeedFragment() : BaseFragment(R.layout.fragment_feed) private fun observeGetExampleItemsState() { viewModel.getExampleItemsState.flowWithLifecycle(lifecycle).onEach { state -> + if (state == UiState.Loading) { + setStatusBarColor(R.color.background_50) + binding.layoutLoading.isVisible = true + } else { + setStatusBarColor(R.color.background_white) + binding.layoutLoading.isVisible = false + } when (state) { is UiState.Success -> adapter.addItemList(state.data) is UiState.Failure -> toast(stringOf(R.string.error_msg)) diff --git a/presentation/src/main/java/kr/genti/presentation/result/waiting/WaitingActivity.kt b/presentation/src/main/java/kr/genti/presentation/result/waiting/WaitingActivity.kt index 150a82a3..48fb7f91 100644 --- a/presentation/src/main/java/kr/genti/presentation/result/waiting/WaitingActivity.kt +++ b/presentation/src/main/java/kr/genti/presentation/result/waiting/WaitingActivity.kt @@ -1,10 +1,12 @@ package kr.genti.presentation.result.waiting +import android.app.Activity import android.os.Bundle import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.ForegroundColorSpan import android.text.style.TextAppearanceSpan +import androidx.activity.OnBackPressedCallback import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -22,16 +24,29 @@ class WaitingActivity : BaseActivity(R.layout.activity_wait super.onCreate(savedInstanceState) initReturnBtnListener() + setOnBackPressed() setStatusBarTransparent() setEmphasizedText() } private fun initReturnBtnListener() { binding.btnWaitReturn.setOnSingleClickListener { + setResult(Activity.RESULT_OK) finish() } } + private fun setOnBackPressed() { + val onBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + setResult(Activity.RESULT_OK) + finish() + } + } + this.onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + } + private fun setStatusBarTransparent() { WindowCompat.setDecorFitsSystemWindows(window, false) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> diff --git a/presentation/src/main/res/layout/fragment_create.xml b/presentation/src/main/res/layout/fragment_create.xml index 4ba4f888..3c20d3e4 100644 --- a/presentation/src/main/res/layout/fragment_create.xml +++ b/presentation/src/main/res/layout/fragment_create.xml @@ -67,6 +67,31 @@ app:layout_constraintTop_toBottomOf="@id/progressbar_create" app:navGraph="@navigation/navigation_create" /> + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_feed.xml b/presentation/src/main/res/layout/fragment_feed.xml index 3836da94..79b297bc 100644 --- a/presentation/src/main/res/layout/fragment_feed.xml +++ b/presentation/src/main/res/layout/fragment_feed.xml @@ -48,5 +48,29 @@ app:layout_constraintTop_toBottomOf="@id/iv_feed_logo" tools:listitem="@layout/item_feed_item" /> + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/fragment_pose.xml b/presentation/src/main/res/layout/fragment_pose.xml index 47fdfdc1..b5e367b4 100644 --- a/presentation/src/main/res/layout/fragment_pose.xml +++ b/presentation/src/main/res/layout/fragment_pose.xml @@ -11,8 +11,8 @@ type="kr.genti.presentation.main.create.CreateViewModel" /> + name="pictureRatio" + type="kr.genti.domain.enums.PictureRatio" /> + app:layout_constraintTop_toTopOf="parent" /> #B3121212 #FFFFFF #F9F9F9 + #7D7D7D #00FF20