Skip to content

Commit

Permalink
[Merge] #49 -> develop
Browse files Browse the repository at this point in the history
[Feat/#49] gyroscope sensor
  • Loading branch information
chattymin authored Sep 10, 2024
2 parents 3bdb033 + 94a149c commit a65ff4c
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class RhythmFragment : BaseFragment<FragmentRhythmBinding>(R.layout.fragment_rhy
initPlayBtnListener()
initStopBtnListener()
initWearableSyncBtnListener()
observeStepCount()
observeRhythmLevel()
observeRhythmUrlState()
observeDownloadState()
Expand Down Expand Up @@ -119,6 +120,12 @@ class RhythmFragment : BaseFragment<FragmentRhythmBinding>(R.layout.fragment_rhy
}
}

private fun observeStepCount() {
viewModel.stepCount.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach { level ->
binding.tvRhythmStep.text = viewModel.stepCount.value.toString()
}.launchIn(lifecycleScope)
}

private fun observeRhythmLevel() {
viewModel.rhythmLevel.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach { level ->
if (level == LEVEL_UNDEFINED) return@onEach
Expand Down Expand Up @@ -237,8 +244,7 @@ class RhythmFragment : BaseFragment<FragmentRhythmBinding>(R.layout.fragment_rhy
}

private fun observeRecordSaveState() {
viewModel.isRecordSaved.flowWithLifecycle(lifecycle).distinctUntilChanged()
.onEach { isSuccess ->
viewModel.isRecordSaved.flowWithLifecycle(lifecycle).onEach { isSuccess ->
if (isSuccess) {
toast(stringOf(R.string.rhythm_toast_save_success))
} else {
Expand Down Expand Up @@ -293,10 +299,6 @@ class RhythmFragment : BaseFragment<FragmentRhythmBinding>(R.layout.fragment_rhy
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_STEP_DETECTOR) {
viewModel.addStepCount(1)

if (viewModel.stepCount.value % SPEED_CALC_INTERVAL == 0) {
calculateSpeed()
}
}
}

Expand All @@ -313,28 +315,16 @@ class RhythmFragment : BaseFragment<FragmentRhythmBinding>(R.layout.fragment_rhy
event.dataItem.also { item ->
if (item.uri.path?.compareTo(PATH_RECORD) == 0) {
DataMapItem.fromDataItem(item).dataMap.apply {
val record = getInt(KEY_RECORD)
val record = getDouble(KEY_RECORD)
Timber.tag("okhttp").d("LISTENER : DATA RECEIVED : $record")
// TODO 여기서 기록 받아서 서버통신으로 기록
viewModel.posRhythmRecordToSaveWatch(record)
}
}
}
}
}
}

private fun calculateSpeed() {
val currentTime = System.currentTimeMillis()
val lastStepTime = viewModel.lastStepTime.value
if (lastStepTime != 0L) {
val timeDiff = currentTime - lastStepTime
val speed = (SPEED_CALC_INTERVAL / (timeDiff / 1000.0)) * 60 // 분당 걸음 수

viewModel.setSpeed(speed)
}
viewModel.setLastStepTime(currentTime)
}

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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
Expand Down Expand Up @@ -44,10 +45,7 @@ constructor(
private val _stepCount = MutableStateFlow(0)
val stepCount: StateFlow<Int> = _stepCount

private val _speed = MutableStateFlow(0.0)

private val _lastStepTime = MutableStateFlow(0L)
val lastStepTime: StateFlow<Long> = _lastStepTime
private val _firstStepTime = MutableStateFlow(0L)

init {
initRhythmLevelFromDataStore()
Expand All @@ -65,14 +63,6 @@ constructor(
_stepCount.value += newStepCount
}

fun setSpeed(newSpeed: Double) {
_speed.value = newSpeed
}

fun setLastStepTime(newLastStepTime: Long) {
_lastStepTime.value = newLastStepTime
}

fun setTempRhythmLevel(level: Int) {
isSubmitted = false
tempRhythmLevel.value = level
Expand Down Expand Up @@ -112,6 +102,7 @@ constructor(
rhythmRepository.getRhythmWav(url)
.onSuccess {
_downloadWavState.value = UiState.Success(it)
_firstStepTime.value = System.currentTimeMillis()
}
.onFailure {
_downloadWavState.value = UiState.Failure(it.message.toString())
Expand All @@ -120,10 +111,35 @@ constructor(
}

fun posRhythmRecordToSave() {
var accuracy =
(stepCount.value.toDouble() / (bpm / 60 * ((System.currentTimeMillis() - _firstStepTime.value) / 10000)))
if (accuracy > 1) {
accuracy = max(2 - accuracy, 0.0)
}

viewModelScope.launch {
rhythmRepository.postRhythmRecord(
RecordRequestModel(
accuracy,
0,
stepCount.value
)
).onSuccess {
resetStepInfo()
_isRecordSaved.emit(true)
}.onFailure {
_isRecordSaved.emit(false)
}
}
}

fun posRhythmRecordToSaveWatch(
accuracy: Double,
) {
viewModelScope.launch {
rhythmRepository.postRhythmRecord(
RecordRequestModel(
_speed.value / (_stepCount.value / OnboardingViewModel.SPEED_CALC_INTERVAL + 1),
accuracy,
0,
stepCount.value
)
Expand All @@ -138,8 +154,7 @@ constructor(

private fun resetStepInfo() {
_stepCount.value = 0
_speed.value = 0.0
_lastStepTime.value = 0L
_firstStepTime.value = 0L
}

fun getBpmFromDataStore() = userRepository.getBpm()
Expand Down
1 change: 1 addition & 0 deletions stempo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

<uses-feature android:name="android.hardware.type.watch" />

<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
Expand Down
33 changes: 20 additions & 13 deletions stempo/src/main/java/com/kkkk/stempo/presentation/WatchActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package com.kkkk.stempo.presentation
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.google.android.gms.wearable.DataClient
import com.google.android.gms.wearable.DataEvent
Expand All @@ -17,12 +19,15 @@ import com.google.android.gms.wearable.DataMapItem
import com.google.android.gms.wearable.Wearable
import com.kkkk.stempo.presentation.home.HomeScreen
import com.kkkk.stempo.presentation.manager.WearableDataManager
import com.kkkk.stempo.presentation.manager.WearableDataManager.Companion.KEY_RECORD
import com.kkkk.stempo.presentation.manager.WearableDataManager.Companion.PATH_RECORD
import com.kkkk.stempo.presentation.theme.StempoandroidTheme
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber

val LocalWearableDataManager =
staticCompositionLocalOf<WearableDataManager> {
error("No DataClient provided")
}

@AndroidEntryPoint
class WatchActivity : ComponentActivity(), DataClient.OnDataChangedListener {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -33,17 +38,18 @@ class WatchActivity : ComponentActivity(), DataClient.OnDataChangedListener {
setTheme(android.R.style.Theme_DeviceDefault)

setContent {
StempoandroidTheme {
HomeScreen()
CompositionLocalProvider(
LocalWearableDataManager provides WearableDataManager(
Wearable.getDataClient(
this
)
)
) {
StempoandroidTheme {
HomeScreen()
}
}
}

// TODO 이 함수로 정지 시 결과값 전송
WearableDataManager(Wearable.getDataClient(this)).sendIntToPhone(
PATH_RECORD,
KEY_RECORD,
50
)
}

override fun onResume() {
Expand All @@ -67,7 +73,7 @@ class WatchActivity : ComponentActivity(), DataClient.OnDataChangedListener {
DataMapItem.fromDataItem(item).dataMap.apply {
val bpm = getInt(KEY_BPM)
Timber.tag("okhttp").d("LISTENER : DATA RECEIVED : $bpm")
// TODO 여기서 bpm 받아서 초기값으로 설정
VIBRATION_INTERVAL = (bpm/60.0).toLong()
}
}
}
Expand All @@ -77,7 +83,8 @@ class WatchActivity : ComponentActivity(), DataClient.OnDataChangedListener {

companion object {
const val KEY_BPM = "KEY_BPM"

const val PATH_BPM = "/bpm"

var VIBRATION_INTERVAL = 500L
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package com.kkkk.stempo.presentation.home

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.PowerManager
import android.os.VibrationEffect
import android.os.Vibrator
import androidx.compose.foundation.Image
Expand All @@ -19,21 +29,82 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import com.kkkk.stempo.R
import com.kkkk.stempo.presentation.LocalWearableDataManager
import com.kkkk.stempo.presentation.home.HomeViewModel.Companion.VIBRATION_DURATION
import com.kkkk.stempo.presentation.manager.WearableDataManager.Companion.KEY_RECORD
import com.kkkk.stempo.presentation.manager.WearableDataManager.Companion.PATH_RECORD

@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel(),
) {
val wearableDataManager = LocalWearableDataManager.current
lateinit var sensorManager: SensorManager
lateinit var sensorEventListener: SensorEventListener

val context = LocalContext.current
val state by viewModel.state.collectAsStateWithLifecycle()
val lifecycleOwner = LocalLifecycleOwner.current
val vibrator = LocalContext.current.getSystemService(Vibrator::class.java)

val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock =
powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "WearOS:KeepScreenOnWakeLock")

sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_STEP_DETECTOR) {
if (!state.isPlayingMusic) return

viewModel.addStep()
}
}

override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
// 정확도 변경 처리 (필요한 경우)
}
}

LaunchedEffect(key1 = Unit) {
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val stepDetectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)

sensorManager.registerListener(
sensorEventListener,
stepDetectorSensor,
SensorManager.SENSOR_DELAY_NORMAL
)
}


LaunchedEffect(Unit) { // 화면 꺼짐 방지
wakeLock.acquire()
}

LaunchedEffect(key1 = Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACTIVITY_RECOGNITION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
context as Activity,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
200
)
}
}
}

LaunchedEffect(viewModel.sideEffect, lifecycleOwner) {
viewModel.sideEffect.flowWithLifecycle(lifecycleOwner.lifecycle).collect { sideEffect ->
when (sideEffect) {
Expand All @@ -46,6 +117,14 @@ fun HomeScreen(
)
)
}

is HomeSideEffect.EndCount -> {
wearableDataManager.sendDoubleToPhone(
PATH_RECORD,
KEY_RECORD,
sideEffect.accuracy
)
}
}
}
}
Expand All @@ -54,11 +133,17 @@ fun HomeScreen(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center
) {
MusicButton(isPlayingMusic = state.isPlayingMusic) {
viewModel.controlMusic()
}

if (state.isPlayingMusic) {
Text(
text = state.stepCount.toString(),
modifier = Modifier.align(Alignment.TopCenter)
)
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.kkkk.stempo.presentation.home

sealed class HomeSideEffect {
data object Vibrate : HomeSideEffect()
data class EndCount(val accuracy: Double) : HomeSideEffect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.kkkk.stempo.presentation.home

data class HomeState(
val isPlayingMusic: Boolean = false,
val stepCount: Int = 0,
)
Loading

0 comments on commit a65ff4c

Please sign in to comment.