From 6613f65c597bdea5b8ba34198c52d6ccba65de1a Mon Sep 17 00:00:00 2001 From: Sangho Kim Date: Tue, 7 Jan 2025 16:53:48 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT/#93]=20=EB=A6=AC=EB=93=ACwav=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/main/rhythm/RhythmScreen.kt | 59 +++++++++++++++++-- .../presentation/main/rhythm/RhythmState.kt | 3 +- .../main/rhythm/RhythmViewModel.kt | 54 ++++++++++++++--- 3 files changed, 102 insertions(+), 14 deletions(-) diff --git a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmScreen.kt b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmScreen.kt index 36b9233..f28d0c8 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmScreen.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmScreen.kt @@ -1,5 +1,7 @@ package com.kkkk.presentation.main.rhythm +import android.media.MediaPlayer +import android.media.SoundPool import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -21,19 +23,26 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.airbnb.lottie.LottieComposition import com.airbnb.lottie.compose.LottieAnimation @@ -51,6 +60,8 @@ import com.kkkk.presentation.main.theme.Transparent50 import com.kkkk.presentation.main.theme.White import com.kkkk.stempo.presentation.R import kotlinx.coroutines.launch +import java.io.File +import java.nio.file.Files @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -59,6 +70,15 @@ fun RhythmRoute( ) { val rhythmState by viewModel.rhythmState.collectAsStateWithLifecycle() + val lifecycleOwner = LocalLifecycleOwner.current + val context = LocalContext.current + + val soundPool = remember { SoundPool.Builder().setMaxStreams(1).build() } + val mediaPlayer = remember { MediaPlayer.create(context, R.raw.music_stretch) } + + var beatSound by remember { mutableIntStateOf(0) } + var beatStream by remember { mutableIntStateOf(0) } + val lottiePlaying by rememberLottieComposition( LottieCompositionSpec.RawRes(rhythmState.lottieResource) ) @@ -66,9 +86,7 @@ fun RhythmRoute( LottieCompositionSpec.RawRes(R.raw.stempo_loading) ) - val sheetState = rememberModalBottomSheetState( - skipPartiallyExpanded = true, - ) + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() val screenHeight = LocalConfiguration.current.screenHeightDp.dp @@ -82,6 +100,29 @@ fun RhythmRoute( ) } + LaunchedEffect(rhythmState.bit, rhythmState.bpm) { + if (File(context.filesDir, rhythmState.filename).exists()) { + viewModel.setMusicPlayer() + } else { + viewModel.getRhythmUrlState() + } + } + + LaunchedEffect(rhythmState.rhythmWav) { + runCatching { + Files.newOutputStream( + File(context.filesDir, rhythmState.filename).toPath() + ).use { outputStream -> + outputStream.write(rhythmState.rhythmWav) + outputStream.flush() + } + }.onSuccess { + viewModel.setMusicPlayer() + }.onFailure { + // toast(stringOf(R.string.error_msg)) + } + } + RhythmScreen( rhythmState = rhythmState, lottiePlaying = lottiePlaying, @@ -121,7 +162,17 @@ internal fun RhythmScreen( onChangeBtnClick: () -> Unit = {} ) { Box( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .pointerInput(rhythmState.isLoading) { + if (rhythmState.isLoading) { + awaitPointerEventScope { + while (rhythmState.isLoading) { + awaitPointerEvent(PointerEventPass.Initial) + } + } + } + }, contentAlignment = Alignment.Center ) { RhythmPlayBtnWithLottie( diff --git a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmState.kt b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmState.kt index 5e2a034..d613775 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmState.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmState.kt @@ -13,7 +13,8 @@ data class RhythmState( val isBottomSheetVisible: Boolean = false, val bit: Int = MIN_BIT, val bpm: Int = MIN_BPM, - val stepCount: Int = 0 + val stepCount: Int = 0, + val rhythmWav: ByteArray? = null, ) { val filename: String get() = "stempo_bpm_${bpm}_bit_${bit}" diff --git a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmViewModel.kt b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmViewModel.kt index 248ee5e..2e947bf 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmViewModel.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmViewModel.kt @@ -1,12 +1,15 @@ package com.kkkk.presentation.main.rhythm import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.kkkk.domain.entity.request.RhythmRequestModel import com.kkkk.domain.repository.RhythmRepository import com.kkkk.domain.repository.UserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -30,25 +33,58 @@ constructor( } fun changeSelectedMode(selectedMode: RhythmMode) { - _rhythmState.update { - it.copy(selectedMode = selectedMode) - } + _rhythmState.update { it.copy(selectedMode = selectedMode, isPlaying = false) } } fun changeIsPlaying(isPlaying: Boolean) { - _rhythmState.update { - it.copy(isPlaying = isPlaying) - } + _rhythmState.update { it.copy(isPlaying = isPlaying) } + } + + fun changeIsLoading(isLoading: Boolean) { + _rhythmState.update { it.copy(isLoading = isLoading) } } fun showBottomSheet(show: Boolean) { - _rhythmState.update { it.copy(isBottomSheetVisible = show) } + _rhythmState.update { it.copy(isBottomSheetVisible = show, isPlaying = false) } } fun updateRhythm(bit: Int, bpm: Int) { - _rhythmState.update { it.copy(bit = bit, bpm = bpm) } + _rhythmState.update { it.copy(bit = bit, bpm = bpm, isPlaying = false) } userRepository.setBpm(bpm) userRepository.setBit(bit) - changeIsPlaying(false) + } + + fun getRhythmUrlState() { + changeIsLoading(true) + viewModelScope.launch { + rhythmRepository.postToGetRhythmUrl( + RhythmRequestModel( + rhythmState.value.bpm, + rhythmState.value.bit + ) + ) + .onSuccess { + getRhythmWavFile(it) + } + .onFailure { + // _rhythmUrlState.value = UiState.Failure(it.message.toString()) + } + } + } + + private fun getRhythmWavFile(url: String) { + viewModelScope.launch { + rhythmRepository.getRhythmWav(url) + .onSuccess { wav -> + _rhythmState.update { it.copy(rhythmWav = wav) } + } + .onFailure { + // _downloadWavState.value = UiState.Failure(it.message.toString()) + } + } + } + + fun setMusicPlayer() { + } } \ No newline at end of file