Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR/#155] 사진 생성 요청뷰 UI 리디자인 #156

Merged
merged 6 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/src/main/java/kr/genti/core/extension/ViewExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import android.graphics.LinearGradient
import android.graphics.RenderEffect
import android.graphics.Shader
import android.os.Build
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ImageSpan
import android.view.View
import android.widget.TextView

Expand Down Expand Up @@ -44,3 +47,10 @@ fun TextView.setGradientText(startColor: Int, endColor: Int) {
Shader.TileMode.CLAMP
)
}

fun TextView.setTextWithImage(text: String, imageResId: Int) {
val spannableString = SpannableString(" $text")
val imageSpan = ImageSpan(context, imageResId, ImageSpan.ALIGN_BOTTOM)
spannableString.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
this.text = spannableString
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,16 @@ import kr.genti.domain.enums.ShotCoverage
data class CreateRequestDto(
@SerialName("prompt")
val prompt: String,
@SerialName("posePicture")
val posePicture: KeyRequestDto?,
@SerialName("facePictureList")
val facePictureList: List<KeyRequestDto>,
@SerialName("cameraAngle")
val cameraAngle: CameraAngle,
@SerialName("shotCoverage")
val shotCoverage: ShotCoverage,
@SerialName("pictureRatio")
val pictureRatio: PictureRatio,
) {
companion object {
fun CreateRequestModel.toDto() =
CreateRequestDto(
prompt,
posePicture?.toDto(),
facePictureList.map { it.toDto() },
cameraAngle,
shotCoverage,
pictureRatio,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import kr.genti.domain.enums.ShotCoverage

data class CreateRequestModel(
val prompt: String,
val posePicture: KeyRequestModel?,
val facePictureList: List<KeyRequestModel>,
val cameraAngle: CameraAngle,
val shotCoverage: ShotCoverage,
val pictureRatio: PictureRatio,
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@ import kr.genti.domain.entity.request.CreateRequestModel
import kr.genti.domain.entity.request.KeyRequestModel
import kr.genti.domain.entity.request.S3RequestModel
import kr.genti.domain.entity.response.ImageFileModel
import kr.genti.domain.entity.response.ImageFileModel.Companion.emptyImageFileModel
import kr.genti.domain.entity.response.S3PresignedUrlModel
import kr.genti.domain.enums.CameraAngle
import kr.genti.domain.enums.FileType
import kr.genti.domain.enums.PictureRatio
import kr.genti.domain.enums.ShotCoverage
import kr.genti.domain.repository.CreateRepository
import kr.genti.domain.repository.UploadRepository
import javax.inject.Inject
import kotlin.random.Random

@HiltViewModel
class CreateViewModel
Expand All @@ -33,12 +29,9 @@ constructor(
private val uploadRepository: UploadRepository,
) : ViewModel() {
val prompt = MutableLiveData<String>()
var plusImage = emptyImageFileModel()
val isWritten = MutableLiveData(false)

val selectedRatio = MutableLiveData<PictureRatio>()
val selectedAngle = MutableLiveData<CameraAngle>()
val selectedCoverage = MutableLiveData<ShotCoverage>()
val isSelected = MutableLiveData(false)

var imageList = listOf<ImageFileModel>()
Expand All @@ -47,12 +40,9 @@ constructor(
private val _currentPercent = MutableStateFlow<Int>(33)
val currentPercent: StateFlow<Int> = _currentPercent

private var currentPrompt: String = ""

private val _totalGeneratingState = MutableStateFlow<UiState<Boolean>>(UiState.Empty)
val totalGeneratingState: StateFlow<UiState<Boolean>> = _totalGeneratingState

private var plusImageS3Key = KeyRequestModel(null)
private var imageS3KeyList = listOf<KeyRequestModel>()

fun modCurrentPercent(amount: Int) {
Expand All @@ -65,32 +55,14 @@ constructor(

fun selectRatio(item: PictureRatio) {
selectedRatio.value = item
checkSelected()
}

fun selectAngle(item: CameraAngle) {
selectedAngle.value = item
checkSelected()
}

fun selectFrame(item: ShotCoverage) {
selectedCoverage.value = item
checkSelected()
}

private fun checkSelected() {
isSelected.value =
selectedRatio.value != null && selectedAngle.value != null && selectedCoverage.value != null
isSelected.value = selectedRatio.value != null
}

fun startSendingImages() {
_totalGeneratingState.value = UiState.Loading
viewModelScope.launch {
runCatching {
listOfNotNull(
if (plusImage.id != (-1).toLong()) async { getSingleS3Url() } else null,
async { getMultiS3Urls() },
).awaitAll()
getMultiS3Urls()
}.onSuccess {
postToGenerateImage()
}.onFailure {
Expand All @@ -99,18 +71,6 @@ constructor(
}
}

private suspend fun getSingleS3Url() {
createRepository.getS3SingleUrl(
S3RequestModel(FileType.USER_UPLOADED_IMAGE, plusImage.name),
)
.onSuccess { uriModel ->
plusImageS3Key = KeyRequestModel(uriModel.s3Key)
postSingleImage(uriModel)
}.onFailure {
_totalGeneratingState.value = UiState.Failure(it.message.toString())
}
}

private suspend fun getMultiS3Urls() {
createRepository.getS3MultiUrl(
listOf(
Expand All @@ -126,13 +86,6 @@ constructor(
}
}

private suspend fun postSingleImage(urlModel: S3PresignedUrlModel) {
uploadRepository.uploadImage(urlModel.url, plusImage.url)
.onFailure {
_totalGeneratingState.value = UiState.Failure(it.message.toString())
}
}

private suspend fun postMultiImage(urlModelList: List<S3PresignedUrlModel>) {
urlModelList.mapIndexed { i, urlModel ->
viewModelScope.async {
Expand All @@ -149,10 +102,7 @@ constructor(
createRepository.postToCreate(
CreateRequestModel(
prompt.value ?: return@launch,
plusImageS3Key,
imageS3KeyList,
selectedAngle.value ?: return@launch,
selectedCoverage.value ?: return@launch,
selectedRatio.value ?: return@launch,
),
)
Expand All @@ -168,12 +118,6 @@ constructor(
_totalGeneratingState.value = UiState.Empty
}

fun getRandomPrompt(): String {
val randomPrompt = promptList[Random.nextInt(promptList.size)]
if (randomPrompt != currentPrompt) currentPrompt = randomPrompt
return currentPrompt
}

companion object {
val promptList =
listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import androidx.viewpager2.widget.ViewPager2
import dagger.hilt.android.AndroidEntryPoint
import kr.genti.core.base.BaseFragment
import kr.genti.core.extension.setOnSingleClickListener
import kr.genti.core.extension.setTextWithImage
import kr.genti.core.extension.stringOf
import kr.genti.presentation.R
import kr.genti.presentation.create.CreateViewModel.Companion.promptList
import kr.genti.presentation.databinding.FragmentDefineBinding
Expand Down Expand Up @@ -37,7 +39,17 @@ class DefineFragment() : BaseFragment<FragmentDefineBinding>(R.layout.fragment_d
}

private fun initView() {
binding.vm = viewModel
with(binding) {
vm = viewModel
tvCreateScriptSubtitle1.setTextWithImage(
stringOf(R.string.create_tv_script_subtitle_1),
R.drawable.ic_check,
)
tvCreateScriptSubtitle2.setTextWithImage(
stringOf(R.string.create_tv_script_subtitle_2),
R.drawable.ic_check,
)
}
}

private fun initCreateBtnListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.BulletSpan
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
Expand All @@ -27,6 +23,7 @@ import kotlinx.coroutines.flow.onEach
import kr.genti.core.base.BaseFragment
import kr.genti.core.extension.getFileName
import kr.genti.core.extension.setOnSingleClickListener
import kr.genti.core.extension.setTextWithImage
import kr.genti.core.extension.stringOf
import kr.genti.core.extension.toast
import kr.genti.core.state.UiState
Expand All @@ -39,7 +36,6 @@ import kr.genti.presentation.util.AmplitudeManager
import kr.genti.presentation.util.AmplitudeManager.EVENT_CLICK_BTN
import kr.genti.presentation.util.AmplitudeManager.PROPERTY_BTN
import kr.genti.presentation.util.AmplitudeManager.PROPERTY_PAGE
import kotlin.math.max

@AndroidEntryPoint
class SelfieFragment : BaseFragment<FragmentSelfieBinding>(R.layout.fragment_selfie) {
Expand All @@ -61,8 +57,6 @@ class SelfieFragment : BaseFragment<FragmentSelfieBinding>(R.layout.fragment_sel
initRequestCreateBtnListener()
setGalleryImageWithPhotoPicker()
setGalleryImageWithGalleryPicker()
setBulletPointList()
setGuideListBlur()
initWaitingResult()
observeGeneratingState()
}
Expand All @@ -74,7 +68,21 @@ class SelfieFragment : BaseFragment<FragmentSelfieBinding>(R.layout.fragment_sel
}

private fun initView() {
binding.vm = viewModel
with(binding) {
vm = viewModel
tvSelfieGuide1.setTextWithImage(
stringOf(R.string.selfie_tv_guide_1),
R.drawable.ic_check,
)
tvSelfieGuide2.setTextWithImage(
stringOf(R.string.selfie_tv_guide_2),
R.drawable.ic_check,
)
tvSelfieGuide3.setTextWithImage(
stringOf(R.string.selfie_tv_guide_3),
R.drawable.ic_check,
)
}
}

private fun initBackPressedListener() {
Expand All @@ -89,28 +97,18 @@ class SelfieFragment : BaseFragment<FragmentSelfieBinding>(R.layout.fragment_sel
}

private fun initAddImageBtnListener() {
with(binding) {
btnSelfieAdd.setOnSingleClickListener {
AmplitudeManager.trackEvent(
EVENT_CLICK_BTN,
mapOf(PROPERTY_PAGE to "create3"),
mapOf(PROPERTY_BTN to "selectpic"),
)
checkAndGetImages()
}
layoutAddedImage.setOnSingleClickListener {
AmplitudeManager.trackEvent(
EVENT_CLICK_BTN,
mapOf(PROPERTY_PAGE to "create3"),
mapOf(PROPERTY_BTN to "reselectpic"),
)
checkAndGetImages()
}
binding.btnSelfieAdd.setOnSingleClickListener {
AmplitudeManager.trackEvent(
EVENT_CLICK_BTN,
mapOf(PROPERTY_PAGE to "create3"),
mapOf(PROPERTY_BTN to "selectpic"),
)
checkAndGetImages()
}
}

private fun initRequestCreateBtnListener() {
binding.btnSelfieNext.setOnSingleClickListener {
binding.btnCreate.setOnSingleClickListener {
AmplitudeManager.trackEvent(
EVENT_CLICK_BTN,
mapOf(PROPERTY_PAGE to "create3"),
Expand Down Expand Up @@ -177,62 +175,22 @@ class SelfieFragment : BaseFragment<FragmentSelfieBinding>(R.layout.fragment_sel
}
isCompleted.value = uris.size == 3
}
with(binding) {
listOf(ivAddedImage1, ivAddedImage2, ivAddedImage3).apply {
forEach { it.setImageDrawable(null) }
uris.take(size).forEachIndexed { index, uri ->
this[index].load(uri)
}
}
}
binding.layoutAddedImage.isVisible = uris.isNotEmpty()
setSavedImages()
}

private fun setSavedImages() {
if (viewModel.imageList.isNotEmpty()) {
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) }
binding.layoutAddedImage.isVisible = true
}
}

private fun setBulletPointList() {
val points =
listOf(
stringOf(R.string.selfie_tv_guide_one),
stringOf(R.string.selfie_tv_guide_two),
stringOf(R.string.selfie_tv_guide_three),
stringOf(R.string.selfie_tv_guide_four),
)
val spannableStringBuilder = SpannableStringBuilder()
points.forEach { point ->
val spannableString =
SpannableString(point).apply {
setSpan(
BulletSpan(15),
0,
point.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
with(spannableStringBuilder) {
append(spannableString)
append("\n")
}
}
binding.tvSelfieGuideBody.text = spannableStringBuilder
}

private fun setGuideListBlur() {
with(binding) {
svSelfieGuide.setOnScrollChangeListener { _, _, scrollY, _, _ ->
ivSelfieBlurBottom.alpha = max(0.0, (1 - scrollY / 500f).toDouble()).toFloat()
ivSelfieBlurTop.alpha = 1 - max(0.0, (1 - scrollY / 100f).toDouble()).toFloat()
listOf(ivAddedImage1, ivAddedImage2, ivAddedImage3).apply {
forEach { it.setImageDrawable(null) }
viewModel.imageList.take(size).forEachIndexed { index, file ->
this[index].load(file.url)
}
}
layoutAddedImage.isVisible = viewModel.imageList.isNotEmpty()
layoutExampleImage.isVisible = viewModel.imageList.isEmpty()
btnSelfieAdd.text =
if (viewModel.imageList.isEmpty()) stringOf(R.string.selfie_tv_btn_select)
else stringOf(R.string.selfie_tv_btn_reselect)
}
}

Expand Down
Loading
Loading