diff --git a/data/src/main/java/com/kkkk/data/local/UserSharedPref.kt b/data/src/main/java/com/kkkk/data/local/UserSharedPref.kt index c1694f4..4eedd20 100644 --- a/data/src/main/java/com/kkkk/data/local/UserSharedPref.kt +++ b/data/src/main/java/com/kkkk/data/local/UserSharedPref.kt @@ -5,6 +5,7 @@ interface UserSharedPref { var refreshToken: String var deviceToken: String var bpm: Int + var bit : Int fun clearInfo() } diff --git a/data/src/main/java/com/kkkk/data/local/UserSharedPrefImpl.kt b/data/src/main/java/com/kkkk/data/local/UserSharedPrefImpl.kt index 954eddd..afb7b27 100644 --- a/data/src/main/java/com/kkkk/data/local/UserSharedPrefImpl.kt +++ b/data/src/main/java/com/kkkk/data/local/UserSharedPrefImpl.kt @@ -22,8 +22,12 @@ constructor( set(value) = dataStore.edit { putString(DEVICE_TOKEN, value) } override var bpm: Int - get() = dataStore.getInt(BPM_LEVEL, 50) - set(value) = dataStore.edit { putInt(BPM_LEVEL, value) } + get() = dataStore.getInt(BPM, 65) + set(value) = dataStore.edit { putInt(BPM, value) } + + override var bit: Int + get() = dataStore.getInt(BIT, 2) + set(value) = dataStore.edit { putInt(BIT, value) } override fun clearInfo() { dataStore.edit().clear().apply() @@ -33,6 +37,7 @@ constructor( private const val ACCESS_TOKEN = "ACCESS_TOKEN" private const val REFRESH_TOKEN = "REFRESH_TOKEN" private const val DEVICE_TOKEN = "DEVICE_TOKEN" - private const val BPM_LEVEL = "BPM_LEVEL" + private const val BPM = "BPM" + private const val BIT = "BIT" } } diff --git a/data/src/main/java/com/kkkk/data/repositoryImpl/UserRepositoryImpl.kt b/data/src/main/java/com/kkkk/data/repositoryImpl/UserRepositoryImpl.kt index 1b908d3..f45b1dc 100644 --- a/data/src/main/java/com/kkkk/data/repositoryImpl/UserRepositoryImpl.kt +++ b/data/src/main/java/com/kkkk/data/repositoryImpl/UserRepositoryImpl.kt @@ -13,9 +13,11 @@ constructor( override fun getRefreshToken(): String = userSharedPref.refreshToken + override fun getDeviceToken(): String = userSharedPref.deviceToken + override fun getBpm(): Int = userSharedPref.bpm - override fun getDeviceToken(): String = userSharedPref.deviceToken + override fun getBit(): Int = userSharedPref.bit override fun setTokens( accessToken: String, @@ -25,12 +27,16 @@ constructor( userSharedPref.refreshToken = refreshToken } + override fun setDeviceToken(deviceToken: String) { + userSharedPref.deviceToken = deviceToken + } + override fun setBpm(bpm: Int) { userSharedPref.bpm = bpm } - override fun setDeviceToken(deviceToken: String) { - userSharedPref.deviceToken = deviceToken + override fun setBit(bit: Int) { + userSharedPref.bit = bit } override fun clearInfo() { diff --git a/domain/src/main/kotlin/com/kkkk/domain/repository/UserRepository.kt b/domain/src/main/kotlin/com/kkkk/domain/repository/UserRepository.kt index 2734f5c..d232beb 100644 --- a/domain/src/main/kotlin/com/kkkk/domain/repository/UserRepository.kt +++ b/domain/src/main/kotlin/com/kkkk/domain/repository/UserRepository.kt @@ -5,18 +5,22 @@ interface UserRepository { fun getRefreshToken(): String + fun getDeviceToken(): String + fun getBpm(): Int + fun getBit(): Int + fun setTokens( accessToken: String, refreshToken: String, ) - fun setBpm(bpm: Int) + fun setDeviceToken(deviceToken: String) - fun getDeviceToken(): String + fun setBpm(bpm: Int) - fun setDeviceToken(deviceToken: String) + fun setBit(bit: Int) fun clearInfo() } diff --git a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmBottomSheet.kt b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmBottomSheet.kt index cd5f543..6ed6ccb 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmBottomSheet.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmBottomSheet.kt @@ -1,9 +1,11 @@ package com.kkkk.presentation.main.rhythm -import android.content.DialogInterface import android.os.Bundle import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.fragment.app.activityViewModels +import com.google.android.material.bottomsheet.BottomSheetBehavior import com.kkkk.core.base.BaseBottomSheet import com.kkkk.core.extension.setOnSingleClickListener import kr.genti.presentation.R @@ -16,7 +18,19 @@ class RhythmBottomSheet : override fun onStart() { super.onStart() - dialog?.window?.setBackgroundDrawableResource(R.color.transparent) + dialog?.apply { + window?.setBackgroundDrawableResource(R.color.transparent) + findViewById(com.google.android.material.R.id.design_bottom_sheet)?.let { + it.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { + override fun onGlobalLayout() { + it.viewTreeObserver.removeOnGlobalLayoutListener(this) + it.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT + BottomSheetBehavior.from(it).state = BottomSheetBehavior.STATE_EXPANDED + it.requestLayout() + } + }) + } + } } override fun onViewCreated( @@ -26,18 +40,14 @@ class RhythmBottomSheet : super.onViewCreated(view, savedInstanceState) binding.vm = viewModel + viewModel.setRhythmToTemp() initSubmitBtnListener() } private fun initSubmitBtnListener() { binding.btnSubmitLevel.setOnSingleClickListener { - viewModel.setRhythmLevel() + viewModel.setTempToRhythm() dismiss() } } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - viewModel.resetTempRhythmLevel() - } } \ No newline at end of file diff --git a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmFragment.kt b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmFragment.kt index 5b487a6..9e89fa6 100644 --- a/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmFragment.kt +++ b/presentation/src/main/java/com/kkkk/presentation/main/rhythm/RhythmFragment.kt @@ -20,18 +20,14 @@ import com.google.android.gms.wearable.DataEventBuffer import com.google.android.gms.wearable.DataMapItem import com.google.android.gms.wearable.Wearable import com.kkkk.core.base.BaseFragment -import com.kkkk.core.extension.colorOf -import com.kkkk.core.extension.drawableOf import com.kkkk.core.extension.setOnSingleClickListener import com.kkkk.core.extension.setStatusBarColor import com.kkkk.core.extension.stringOf import com.kkkk.core.extension.toast import com.kkkk.core.state.UiState -import com.kkkk.presentation.main.rhythm.RhythmViewModel.Companion.LEVEL_UNDEFINED import com.kkkk.presentation.manager.PhoneDataManager import com.kkkk.presentation.manager.PhoneDataManager.Companion.KEY_BPM import com.kkkk.presentation.manager.PhoneDataManager.Companion.PATH_BPM -import com.kkkk.presentation.onboarding.onbarding.OnboardingViewModel.Companion.SPEED_CALC_INTERVAL import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn @@ -68,7 +64,7 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy initStopBtnListener() initWearableSyncBtnListener() observeStepCount() - observeRhythmLevel() + observeRhythmChanged() observeRhythmUrlState() observeDownloadState() observeRecordSaveState() @@ -116,7 +112,7 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy private fun initWearableSyncBtnListener() { binding.tvRhythmTitle.setOnSingleClickListener { - phoneDataManager.sendIntToWearable(PATH_BPM, KEY_BPM, viewModel.getBpmFromDataStore()) + phoneDataManager.sendIntToWearable(PATH_BPM, KEY_BPM, viewModel.bpm) } } @@ -126,44 +122,49 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy }.launchIn(lifecycleScope) } - private fun observeRhythmLevel() { - viewModel.rhythmLevel.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach { level -> - if (level == LEVEL_UNDEFINED) return@onEach - if (::mediaPlayer.isInitialized) { - mediaPlayer.pause() - switchPlayingState(false) - } - setUiWithCurrentLevel() - viewModel.postToGetRhythmUrlFromServer() - }.launchIn(lifecycleScope) + private fun observeRhythmChanged() { + viewModel.isRhythmChanged.flowWithLifecycle(lifecycle).distinctUntilChanged() + .onEach { isChanged -> + if (isChanged) { + if (::mediaPlayer.isInitialized) { + mediaPlayer.pause() + switchPlayingState(false) + } + setUiWithCurrentLevel() + viewModel.resetRhythmChangedState() + viewModel.postToGetRhythmUrlFromServer() + } + }.launchIn(lifecycleScope) } private fun setUiWithCurrentLevel() { - val color = when (viewModel.rhythmLevel.value.rem(3)) { - 1 -> COLOR_PURPLE - 2 -> COLOR_SKY - 0 -> COLOR_GREEN + val color = when (viewModel.bit.rem(3)) { + 2 -> COLOR_PURPLE + 3 -> COLOR_SKY + 4 -> COLOR_GREEN + 6 -> COLOR_PURPLE + 8 -> COLOR_SKY else -> return } - with(binding) { - tvRhythmLevel.apply { - text = getString(R.string.rhythm_tv_level, viewModel.rhythmLevel.value) - setTextColor(colorOf(getResource("${color}_50", COLOR))) - background = - drawableOf(getResource("shape_white_fill_${color}50_line_17_rect", DRAWABLE)) - } - tvRhythmStep.apply { - setTextColor(colorOf(getResource("${color}_50", COLOR))) - background = - drawableOf(getResource("shape_white_fill_${color}50_line_17_rect", DRAWABLE)) - } - ivRhythmBg.setImageResource(getResource("img_rhythm_bg_$color", DRAWABLE)) - lottieRhythmBg.apply { - setAnimation(getResource("stempo_rhythm_$color", RAW)) - speed = viewModel.bpm / FLOAT_120 - playAnimation() - } - } +// with(binding) { +// tvRhythmLevel.apply { +// text = getString(R.string.rhythm_tv_level, viewModel.rhythmLevel.value) +// setTextColor(colorOf(getResource("${color}_50", COLOR))) +// background = +// drawableOf(getResource("shape_white_fill_${color}50_line_17_rect", DRAWABLE)) +// } +// tvRhythmStep.apply { +// setTextColor(colorOf(getResource("${color}_50", COLOR))) +// background = +// drawableOf(getResource("shape_white_fill_${color}50_line_17_rect", DRAWABLE)) +// } +// ivRhythmBg.setImageResource(getResource("img_rhythm_bg_$color", DRAWABLE)) +// lottieRhythmBg.apply { +// setAnimation(getResource("stempo_rhythm_$color", RAW)) +// speed = viewModel.bpm / FLOAT_120 +// playAnimation() +// } +// } } private fun getResource(name: String, defType: String) = @@ -245,12 +246,12 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy private fun observeRecordSaveState() { viewModel.isRecordSaved.flowWithLifecycle(lifecycle).onEach { isSuccess -> - if (isSuccess) { - toast(stringOf(R.string.rhythm_toast_save_success)) - } else { - toast(stringOf(R.string.error_msg)) - } - }.launchIn(lifecycleScope) + if (isSuccess) { + toast(stringOf(R.string.rhythm_toast_save_success)) + } else { + toast(stringOf(R.string.error_msg)) + } + }.launchIn(lifecycleScope) } override fun onDestroyView() { 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 d360cd1..a3bdaef 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 @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import javax.inject.Inject -import kotlin.math.max @HiltViewModel class RhythmViewModel @@ -23,13 +22,17 @@ constructor( private val rhythmRepository: RhythmRepository, private val userRepository: UserRepository, ) : ViewModel() { - var tempRhythmLevel = MutableLiveData(1) - var bpm = 50 - var filename: String = "stempo_level_1" - private var isSubmitted: Boolean = true + var bpm = MIN_BPM + var bit = MAX_BIT + var filename: String = "stempo_bpm_${bpm}_bit_${bit}" - private val _rhythmLevel = MutableStateFlow(LEVEL_UNDEFINED) - val rhythmLevel: StateFlow = _rhythmLevel + var tempBpm = MutableLiveData(MIN_BPM) + var tempBit = MutableLiveData(MIN_BIT) + var isBpmMinusAvailable = MutableLiveData(false) + var isBpmPlusAvailable = MutableLiveData(true) + + private val _isRhythmChanged = MutableSharedFlow() + val isRhythmChanged: SharedFlow = _isRhythmChanged private val _rhythmUrlState = MutableStateFlow>(UiState.Empty) val rhythmUrlState: StateFlow> = _rhythmUrlState @@ -58,42 +61,52 @@ constructor( private fun initRhythmLevelFromDataStore() { bpm = userRepository.getBpm() - val currentLevel = setBpmLevel(bpm) - filename = "stempo_level_$currentLevel" - _rhythmLevel.value = currentLevel - tempRhythmLevel.value = currentLevel + bit = userRepository.getBit() + filename = "stempo_bpm_${bpm}_bit_${bit}" } - fun addStepCount(newStepCount: Int) { - if ((_oddStepCount.value + _evenStepCount.value) % 2 == 0) { - _oddStepCount.value += newStepCount - _oddStepTime.value = System.currentTimeMillis() - _beforeStepTime.value - } else { - _evenStepCount.value += newStepCount - _evenStepTime.value = System.currentTimeMillis() - _beforeStepTime.value - } - _stepCount.value += newStepCount - _beforeStepTime.value = System.currentTimeMillis() + fun setTempBpm(bpm: Int) { + tempBpm.value = bpm + isBpmMinusAvailable.value = bpm != MIN_BPM + isBpmPlusAvailable.value = bpm != MAX_BPM } - fun setTempRhythmLevel(level: Int) { - isSubmitted = false - tempRhythmLevel.value = level + fun plusTempBpm() { + if (tempBpm.value == MAX_BPM) return + tempBpm.value = tempBpm.value?.plus(5) + isBpmMinusAvailable.value = tempBpm.value != MIN_BPM + isBpmPlusAvailable.value = tempBpm.value != MAX_BPM } - fun resetTempRhythmLevel() { - if (!isSubmitted) { - isSubmitted = true - tempRhythmLevel.value = rhythmLevel.value - } + fun minusTempBpm() { + if (tempBpm.value == MIN_BPM) return + tempBpm.value = tempBpm.value?.minus(5) + isBpmMinusAvailable.value = tempBpm.value != MIN_BPM + isBpmPlusAvailable.value = tempBpm.value != MAX_BPM } - fun setRhythmLevel() { - isSubmitted = true - filename = "stempo_level_" + tempRhythmLevel.value.toString() - bpm = setBpm(tempRhythmLevel.value ?: 1) + fun setTempBit(bit: Int) { + tempBit.value = bit + } + + fun setRhythmToTemp() { + tempBpm.value = bpm + tempBit.value = bit + } + + fun setTempToRhythm() { + bpm = tempBpm.value ?: MIN_BPM + bit = tempBit.value ?: MIN_BIT + filename = "stempo_bpm_${bpm}_bit_${bit}" userRepository.setBpm(bpm) - _rhythmLevel.value = tempRhythmLevel.value ?: 1 + userRepository.setBit(bit) + viewModelScope.launch { + _isRhythmChanged.emit(true) + } + } + + fun resetRhythmChangedState() { + _isRhythmChanged.resetReplayCache() } fun postToGetRhythmUrlFromServer() { @@ -123,6 +136,18 @@ constructor( } } + fun addStepCount(newStepCount: Int) { + if ((_oddStepCount.value + _evenStepCount.value) % 2 == 0) { + _oddStepCount.value += newStepCount + _oddStepTime.value = System.currentTimeMillis() - _beforeStepTime.value + } else { + _evenStepCount.value += newStepCount + _evenStepTime.value = System.currentTimeMillis() - _beforeStepTime.value + } + _stepCount.value += newStepCount + _beforeStepTime.value = System.currentTimeMillis() + } + fun posRhythmRecordToSave() { val accuracy = calculateAccuracy(_oddStepTime.value, _evenStepTime.value) @@ -179,25 +204,12 @@ constructor( _beforeStepTime.value = 0L } - fun getBpmFromDataStore() = userRepository.getBpm() - - private fun setBpm(level: Int) = 40 + level * 10 - - private fun setBpmLevel(bpm: Int) = - when (bpm) { - in 55..65 -> 2 - in 65..75 -> 3 - in 75..85 -> 4 - in 85..95 -> 5 - in 95..105 -> 6 - in 105..115 -> 7 - in 115..125 -> 8 - in 125..Int.MAX_VALUE -> 9 - else -> 1 - } - companion object { - const val LEVEL_UNDEFINED = -1 + const val MIN_BPM = 65 + const val MAX_BPM = 115 + const val MIN_BIT = 2 + const val MAX_BIT = 8 + const val MAX_ALLOWED_DIFFERENCE = 360000L } } \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_minus_gray.xml b/presentation/src/main/res/drawable/ic_minus_gray.xml new file mode 100644 index 0000000..857fb3f --- /dev/null +++ b/presentation/src/main/res/drawable/ic_minus_gray.xml @@ -0,0 +1,15 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_minus_purple.xml b/presentation/src/main/res/drawable/ic_minus_purple.xml new file mode 100644 index 0000000..1ce6a75 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_minus_purple.xml @@ -0,0 +1,15 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_plus_gray.xml b/presentation/src/main/res/drawable/ic_plus_gray.xml new file mode 100644 index 0000000..0eb0c54 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_plus_gray.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/presentation/src/main/res/drawable/ic_plus_purple.xml b/presentation/src/main/res/drawable/ic_plus_purple.xml new file mode 100644 index 0000000..8803751 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_plus_purple.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/presentation/src/main/res/drawable/shape_gray100_fill_gray300_line_25_rect.xml b/presentation/src/main/res/drawable/shape_gray100_fill_gray300_line_25_rect.xml new file mode 100644 index 0000000..6287545 --- /dev/null +++ b/presentation/src/main/res/drawable/shape_gray100_fill_gray300_line_25_rect.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/shape_gray200_fill_circle.xml b/presentation/src/main/res/drawable/shape_gray200_fill_16_rect.xml similarity index 79% rename from presentation/src/main/res/drawable/shape_gray200_fill_circle.xml rename to presentation/src/main/res/drawable/shape_gray200_fill_16_rect.xml index b6c464f..b5ed002 100644 --- a/presentation/src/main/res/drawable/shape_gray200_fill_circle.xml +++ b/presentation/src/main/res/drawable/shape_gray200_fill_16_rect.xml @@ -1,5 +1,5 @@ - + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/shape_purple50_fill_circle.xml b/presentation/src/main/res/drawable/shape_purple50_fill_16_rect.xml similarity index 79% rename from presentation/src/main/res/drawable/shape_purple50_fill_circle.xml rename to presentation/src/main/res/drawable/shape_purple50_fill_16_rect.xml index 398083d..fe33df5 100644 --- a/presentation/src/main/res/drawable/shape_purple50_fill_circle.xml +++ b/presentation/src/main/res/drawable/shape_purple50_fill_16_rect.xml @@ -1,5 +1,5 @@ - + + \ No newline at end of file diff --git a/presentation/src/main/res/drawable/shape_white_fill_purple50_line_25_rect.xml b/presentation/src/main/res/drawable/shape_white_fill_purple50_line_25_rect.xml new file mode 100644 index 0000000..6222746 --- /dev/null +++ b/presentation/src/main/res/drawable/shape_white_fill_purple50_line_25_rect.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/bottom_sheet_rhythm.xml b/presentation/src/main/res/layout/bottom_sheet_rhythm.xml index a7af9c9..09f6f2f 100644 --- a/presentation/src/main/res/layout/bottom_sheet_rhythm.xml +++ b/presentation/src/main/res/layout/bottom_sheet_rhythm.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -29,147 +30,267 @@ app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@id/tv_rhythm_bit_title"> + android:onClick="@{() -> vm.setTempBit(2)}" + android:paddingVertical="12dp" + android:text="2 비트" + android:textColor="@{vm.tempBit == 2 ? @color/white : @color/gray_600}" + tools:background="@drawable/shape_gray200_fill_16_rect" /> + android:onClick="@{() -> vm.setTempBit(3)}" + android:paddingVertical="12dp" + android:text="3 비트" + android:textColor="@{vm.tempBit == 3 ? @color/white : @color/gray_600}" + tools:background="@drawable/shape_gray200_fill_16_rect" /> + android:onClick="@{() -> vm.setTempBit(4)}" + android:paddingVertical="12dp" + android:text="4 비트" + android:textColor="@{vm.tempBit == 4 ? @color/white : @color/gray_600}" + tools:background="@drawable/shape_gray200_fill_16_rect" /> + + + + + + + + + + + + + + + + + + + + android:onClick="@{() -> vm.setTempBpm(65)}" + android:paddingVertical="12dp" + android:text="65" + android:textColor="@{vm.tempBpm == 65 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> + android:onClick="@{() -> vm.setTempBpm(75)}" + android:paddingVertical="12dp" + android:text="75" + android:textColor="@{vm.tempBpm == 75 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> + android:onClick="@{() -> vm.setTempBpm(85)}" + android:paddingVertical="12dp" + android:text="85" + android:textColor="@{vm.tempBpm == 85 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> + android:onClick="@{() -> vm.setTempBpm(95)}" + android:paddingVertical="12dp" + android:text=" 95 " + android:textColor="@{vm.tempBpm == 95 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> + android:onClick="@{() -> vm.setTempBpm(105)}" + android:paddingVertical="12dp" + android:text="105" + android:textColor="@{vm.tempBpm == 105 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> + android:onClick="@{() -> vm.setTempBpm(115)}" + android:paddingVertical="12dp" + android:text="115" + android:textColor="@{vm.tempBpm == 115 ? @color/purple_50 : @color/gray_500}" + tools:background="@drawable/shape_gray100_fill_gray300_line_25_rect" /> @@ -179,7 +300,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" - android:layout_marginTop="14dp" + android:layout_marginTop="24dp" android:background="@drawable/shape_purple10_fill_12_rect" android:gravity="center" android:paddingVertical="15dp" @@ -187,7 +308,7 @@ android:textColor="@color/purple_50" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/layout_level_grid" /> + app:layout_constraintTop_toBottomOf="@id/layout_grid_bpm" /> \ No newline at end of file