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

Upload UI 상태 #147

Merged
merged 16 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package com.ohdodok.catchytape.core.data.api

import com.ohdodok.catchytape.core.data.model.MusicGenresResponse
import com.ohdodok.catchytape.core.data.model.MusicRequest
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST

interface MusicApi {

@GET("musics/genres")
suspend fun getGenres(): Response<MusicGenresResponse>

@POST("musics")
suspend fun postMusic(
@Body music: MusicRequest
): Response<Unit>

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ohdodok.catchytape.core.data.api

import com.ohdodok.catchytape.core.data.model.UrlResponse
import retrofit2.Response
import retrofit2.http.Headers
import retrofit2.http.POST

interface UploadApi {

@POST("upload/music")
@Headers("Content-Type: audio/mpeg")
suspend fun uploadMusic(
): Response<UrlResponse>

@POST("upload/image")
@Headers("Content-Type: image/png")
suspend fun uploadImage(
): Response<UrlResponse>

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ohdodok.catchytape.core.data.di

import com.ohdodok.catchytape.core.data.api.MusicApi
import com.ohdodok.catchytape.core.data.api.UploadApi
import com.ohdodok.catchytape.core.data.api.UserApi
import dagger.Module
import dagger.Provides
Expand All @@ -24,4 +25,10 @@ object ApiModule {
fun provideMusicApi(retrofit: Retrofit): MusicApi {
return retrofit.create(MusicApi::class.java)
}

@Provides
@Singleton
fun provideUploadApi(retrofit: Retrofit): UploadApi {
return retrofit.create(UploadApi::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.ohdodok.catchytape.core.data.model

data class MusicRequest (
val title: String,
val cover: String,
val file: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.ohdodok.catchytape.core.data.model

data class UrlResponse(
val url: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ohdodok.catchytape.core.domain.usecase

import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.io.File
import javax.inject.Inject

class UploadFileUseCase @Inject constructor(

) {

fun getImgUrl(file: File): Flow<String> = flow {
// todo : 서버 기다리는 중..
delay(1000)
emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/image/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202023-11-21%20180100.png")
}

fun getAudioUrl(file: File): Flow<String> = flow {
// todo : 서버 기다리는 중..
delay(1000)
emit("https://kr.object.ncloudstorage.com/catchy-tape-bucket2/music/2/%EC%9D%B4%EB%85%B8%EB%9E%98.mp3")
}
}
5 changes: 4 additions & 1 deletion android/core/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,13 @@ android {

dependencies {

implementation(project(":core:domain"))

api(libs.material)

api(libs.navigation.fragment.ktx)
api(libs.navigation.ui.ktx)

implementation(project(":core:domain"))
api(libs.glide)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ohdodok.catchytape.core.ui

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide

@BindingAdapter("imageUrl")
fun ImageView.bindImg(url: String) {
Glide.with(this.context)
.load(url)
.into(this)
}
2 changes: 1 addition & 1 deletion android/core/ui/src/main/res/drawable/ic_camera.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
android:viewportHeight="24">
<path
android:pathData="M20,4H16.83L15,2H9L7.17,4H4C3.47,4 2.961,4.211 2.586,4.586C2.211,4.961 2,5.47 2,6V18C2,18.53 2.211,19.039 2.586,19.414C2.961,19.789 3.47,20 4,20H20C20.53,20 21.039,19.789 21.414,19.414C21.789,19.039 22,18.53 22,18V6C22,5.47 21.789,4.961 21.414,4.586C21.039,4.211 20.53,4 20,4ZM20,18H4V6H8.05L9.88,4H14.12L15.95,6H20V18ZM12,7C10.674,7 9.402,7.527 8.464,8.464C7.527,9.402 7,10.674 7,12C7,13.326 7.527,14.598 8.464,15.535C9.402,16.473 10.674,17 12,17C13.326,17 14.598,16.473 15.535,15.535C16.473,14.598 17,13.326 17,12C17,10.674 16.473,9.402 15.535,8.464C14.598,7.527 13.326,7 12,7ZM12,15C11.204,15 10.441,14.684 9.879,14.121C9.316,13.559 9,12.796 9,12C9,11.204 9.316,10.441 9.879,9.879C10.441,9.316 11.204,9 12,9C12.796,9 13.559,9.316 14.121,9.879C14.684,10.441 15,11.204 15,12C15,12.796 14.684,13.559 14.121,14.121C13.559,14.684 12.796,15 12,15Z"
android:fillColor="@color/on_surface"/>
android:fillColor="@color/black"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,38 @@ package com.ohdodok.catchytape.feature.upload
import android.R
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.Button
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter


@BindingAdapter("changeSelectedPosition")
fun AutoCompleteTextView.bindPosition(onChange: (Int) -> Unit) {
setOnItemClickListener { _, _, position, _ -> onChange(position) }
}
Comment on lines -9 to -12
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

position 이 필요한 것 같지 않아서 선택한 텍스트로 판단하도록 바꼈습니당

import com.bumptech.glide.Glide
import com.google.android.material.progressindicator.LinearProgressIndicator

@BindingAdapter("list")
fun AutoCompleteTextView.setAdapter(list: List<String>) {
val adapter = ArrayAdapter(this.context, R.layout.simple_list_item_1, list)
setAdapter(adapter)
}

@BindingAdapter("visible")
fun LinearProgressIndicator.setVisible(uiState: UploadUiState) {
isVisible = uiState.audioState is InputState.Loading || uiState.imageState is InputState.Loading
}

@BindingAdapter("completeBtnEnable")
fun Button.setCompleteBtnEnable(uiState: UploadUiState) {
isEnabled =
uiState.audioState is InputState.Success
&& uiState.imageState is InputState.Success
&& uiState.titleState is InputState.Success
&& uiState.genreState is InputState.Success
}

@BindingAdapter("uploadedThumbnail")
fun ImageView.bindUrl(uiState: UploadUiState) {
if (uiState.imageState is InputState.Success) {
Glide.with(this)
.load(uiState.imageState.value)
.into(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.ohdodok.catchytape.feature.upload

import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.view.View
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.core.net.toFile
import androidx.fragment.app.viewModels
import com.ohdodok.catchytape.catchytape.upload.R
import com.ohdodok.catchytape.catchytape.upload.databinding.FragmentUploadBinding
Expand All @@ -18,28 +19,43 @@ class UploadFragment : BaseFragment<FragmentUploadBinding>(R.layout.fragment_upl

private val imagePickerLauncher = registerForActivityResult(PickVisualMedia()) { uri ->
if (uri == null) return@registerForActivityResult

viewModel.uploadImage(uri.toFile())
viewModel.uploadImage(uri)
}

private val filePickerLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri == null) return@registerForActivityResult

viewModel.uploadAudio(uri.toFile())
binding.btnFile.text = getFileName(uri)
viewModel.uploadAudio(uri)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel

setUpFileBtn()
setupBackStack(binding.tbUpload)
setupSelectThumbnailImage()
}

private fun setUpFileBtn() {
binding.btnFile.setOnClickListener {
filePickerLauncher.launch("audio/*")
}
}

private fun setupSelectThumbnailImage() {
binding.cvUploadThumbnail.setOnClickListener {
imagePickerLauncher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))
}
}

private fun getFileName(uri: Uri): String? {
val contentResolver = requireContext().contentResolver
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
return cursor.getString(nameIndex)
}
return null
}
Comment on lines +53 to +61
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 함수 하는 일이 무엇인가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파일이름을 가져오는 함수입니당 공식문서 참고했어용

}
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
package com.ohdodok.catchytape.feature.upload

import android.net.Uri
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.File
import javax.inject.Inject
import androidx.lifecycle.viewModelScope
import com.ohdodok.catchytape.core.domain.usecase.UploadFileUseCase
import com.ohdodok.catchytape.core.domain.usecase.GetMusicGenresUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.onStart

@HiltViewModel
class UploadViewModel @Inject constructor(
private val getMusicGenresUseCase: GetMusicGenresUseCase
private val getMusicGenresUseCase: GetMusicGenresUseCase,
private val uploadFileUseCase: UploadFileUseCase
) : ViewModel() {

private var uploadedImage: String? = null

val uploadedMusicTitle = MutableStateFlow("")

private val _uploadedMusicGenrePosition = MutableStateFlow(-1)
val uploadedMusicGenrePosition = _uploadedMusicGenrePosition.asStateFlow()

val isUploadEnable: StateFlow<Boolean> =
combine(uploadedMusicTitle, uploadedMusicGenrePosition) { title, genrePosition ->
title.isNotBlank() && genrePosition != -1
}.stateIn(viewModelScope, SharingStarted.Eagerly, false)

val onChangePosition: (Int) -> Unit =
{ position: Int -> _uploadedMusicGenrePosition.value = position }
val musicTitle = MutableStateFlow("")
val musicGenre = MutableStateFlow("")

private val _musicGenres: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
val musicGenres = _musicGenres.asStateFlow()

private val _uploadUiState: MutableStateFlow<UploadUiState> = MutableStateFlow(UploadUiState())
val uploadUiState = _uploadUiState.asStateFlow()

init {
fetchGenres()
observeTitle()
observeGenre()
}

private fun fetchGenres() {
Expand All @@ -48,12 +42,73 @@ class UploadViewModel @Inject constructor(
}.launchIn(viewModelScope)
}

fun uploadImage(imageFile: File) {
// todo : image 파일을 업로드 한다.
// todo : 반환 값을 uploadedImage에 저장한다.
private fun observeTitle() {
musicTitle.onEach {
if (it.isEmpty()) {
_uploadUiState.value = uploadUiState.value.copy(titleState = InputState.Empty)
return@onEach
} else {
_uploadUiState.value =
uploadUiState.value.copy(titleState = InputState.Success(value = it))
}
}.launchIn(viewModelScope)
}

fun uploadAudio(audioFile: File) {
// todo : audio 파일을 업로드 한다.
private fun observeGenre() {
musicGenre.onEach {
if (it.isEmpty()) {
_uploadUiState.value = uploadUiState.value.copy(genreState = InputState.Empty)
return@onEach
} else {
_uploadUiState.value =
uploadUiState.value.copy(genreState = InputState.Success(value = it))
}
}.launchIn(viewModelScope)
}
}

fun uploadImage(imageUri: Uri) {
imageUri.path?.let { path ->
uploadFileUseCase.getImgUrl(File(path)).onStart {
_uploadUiState.value = uploadUiState.value.copy(imageState = InputState.Loading)
}.catch {
// TODO : 에러 처리
_uploadUiState.value =
uploadUiState.value.copy(imageState = InputState.Error)
}.onEach { url ->
_uploadUiState.value =
uploadUiState.value.copy(imageState = InputState.Success(value = url))
}.launchIn(viewModelScope)
}
}

fun uploadAudio(audioUri: Uri) {
audioUri.path?.let { path ->
uploadFileUseCase.getAudioUrl(File(path)).onStart {
_uploadUiState.value = uploadUiState.value.copy(audioState = InputState.Loading)
}.catch {
// TODO : 에러 처리
_uploadUiState.value =
uploadUiState.value.copy(audioState = InputState.Error)
}.onEach {
_uploadUiState.value =
uploadUiState.value.copy(audioState = InputState.Success(value = it))
}.launchIn(viewModelScope)
}
}
}

data class UploadUiState(
val audioState: InputState = InputState.Empty,
val imageState: InputState = InputState.Empty,
val titleState: InputState = InputState.Empty,
val genreState: InputState = InputState.Empty
)

sealed class InputState {
data object Empty : InputState()

data object Loading : InputState()
data class Success(val value: String) : InputState()
data object Error: InputState()
}

Loading