diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 82284de..2b7620b 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -32,6 +32,7 @@ object AndroidXDependencies { const val hilt = "com.google.dagger:hilt-android:${Versions.hiltVersion}" const val ossLicense = "com.google.android.gms:play-services-oss-licenses:${Versions.ossVersion}" + const val wearable = "com.google.android.gms:play-services-wearable:${Versions.wearableVersion}" } object TestDependencies { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 66e803d..c8f4ea0 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -3,7 +3,7 @@ import org.gradle.api.JavaVersion object Versions { const val gradleVersion = "8.0.2" - const val kotlinVersion = "1.8.20" + const val kotlinVersion = "1.9.0" const val kotlinSerializationJsonVersion = "1.5.1" const val kotlinDateTimeVersion = "0.4.0" const val coreKtxVersion = "1.10.1" @@ -20,6 +20,7 @@ object Versions { const val lifecycleVersion = "2.6.1" const val ossPluginVersion = "0.10.4" const val ossVersion = "17.0.0" + const val wearableVersion = "18.2.0" const val splashVersion = "1.0.1" const val workManagerVersion = "2.8.1" const val coilVersion = "2.4.0" diff --git a/core-di/build.gradle.kts b/core-di/build.gradle.kts index c86cbc2..91e73a7 100644 --- a/core-di/build.gradle.kts +++ b/core-di/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(hilt) implementation(workManager) implementation(hiltWorkManager) + implementation(wearable) } KaptDependencies.run { diff --git a/core-di/src/main/java/com/kkkk/di/ManagerModule.kt b/core-di/src/main/java/com/kkkk/di/ManagerModule.kt new file mode 100644 index 0000000..c6cb993 --- /dev/null +++ b/core-di/src/main/java/com/kkkk/di/ManagerModule.kt @@ -0,0 +1,29 @@ +package com.kkkk.di + +import android.content.Context +import com.google.android.gms.wearable.DataClient +import com.google.android.gms.wearable.Wearable +import com.kkkk.presentation.manager.WearableDataManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object ManagerModule{ + + @Provides + @Singleton + fun provideDataClient(@ApplicationContext context: Context): DataClient { + return Wearable.getDataClient(context) + } + + @Provides + @Singleton + fun provideWearableDataManager(dataClient: DataClient): WearableDataManager { + return WearableDataManager(dataClient) + } +} \ No newline at end of file diff --git a/core-ui/build.gradle.kts b/core-ui/build.gradle.kts index 8f8f39e..bd89bfd 100644 --- a/core-ui/build.gradle.kts +++ b/core-ui/build.gradle.kts @@ -30,21 +30,26 @@ android { } dependencies { - // Kotlin - implementation(KotlinDependencies.kotlin) + KotlinDependencies.run { + implementation(kotlin) + } - // Lifecycle Ktx - implementation(AndroidXDependencies.lifeCycleKtx) + AndroidXDependencies.run { + implementation(lifeCycleKtx) + implementation(hilt) + } - // Material Design - implementation(MaterialDesignDependencies.materialDesign) + MaterialDesignDependencies.run { + implementation(materialDesign) + } - // Hilt - implementation(AndroidXDependencies.hilt) - kapt(KaptDependencies.hiltAndroidCompiler) + KaptDependencies.run { + kapt(hiltAndroidCompiler) + } // Test Dependency testImplementation(TestDependencies.jUnit) androidTestImplementation(TestDependencies.androidTest) androidTestImplementation(TestDependencies.espresso) + } \ No newline at end of file diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index b5731eb..0a838d2 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { implementation(splashScreen) implementation(workManager) implementation(hiltWorkManager) + implementation(wearable) } KaptDependencies.run { 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 1378975..0235579 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 @@ -23,6 +23,9 @@ 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.WearableDataManager +import com.kkkk.presentation.manager.WearableDataManager.Companion.KEY_BPM +import com.kkkk.presentation.manager.WearableDataManager.Companion.PATH_BPM import com.kkkk.presentation.onboarding.onbarding.OnboardingViewModel.Companion.SPEED_CALC_INTERVAL import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.distinctUntilChanged @@ -32,6 +35,7 @@ import kr.genti.presentation.R import kr.genti.presentation.databinding.FragmentRhythmBinding import java.io.File import java.nio.file.Files +import javax.inject.Inject @AndroidEntryPoint class RhythmFragment : BaseFragment(R.layout.fragment_rhythm), @@ -44,6 +48,9 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy private var rhythmSaveDialog: RhythmSaveDialog? = null private lateinit var mediaPlayer: MediaPlayer + @Inject + lateinit var wearableDataManager: WearableDataManager + override fun onViewCreated( view: View, savedInstanceState: Bundle?, @@ -53,6 +60,7 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy initChangeLevelBtnListener() initPlayBtnListener() initStopBtnListener() + initWearableSyncBtnListener() observeRhythmLevel() observeRhythmUrlState() observeDownloadState() @@ -99,6 +107,12 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy } } + private fun initWearableSyncBtnListener() { + binding.tvRhythmTitle.setOnSingleClickListener { + wearableDataManager.sendIntToWearable(PATH_BPM, KEY_BPM, viewModel.getBpmFromDataStore()) + } + } + private fun observeRhythmLevel() { viewModel.rhythmLevel.flowWithLifecycle(lifecycle).distinctUntilChanged().onEach { level -> if (level == LEVEL_UNDEFINED) return@onEach @@ -107,7 +121,7 @@ class RhythmFragment : BaseFragment(R.layout.fragment_rhy switchPlayingState(false) } setUiWithCurrentLevel() - viewModel.postToGetRhythmUrlFromServer(level) + viewModel.postToGetRhythmUrlFromServer() }.launchIn(lifecycleScope) } 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 e0070f3..b004278 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 @@ -26,7 +26,7 @@ constructor( var tempRhythmLevel = MutableLiveData(1) var bpm = 50 var filename: String = "stempo_level_1" - var isSubmitted: Boolean = true + private var isSubmitted: Boolean = true private val _rhythmLevel = MutableStateFlow(LEVEL_UNDEFINED) val rhythmLevel: StateFlow = _rhythmLevel @@ -93,7 +93,7 @@ constructor( _rhythmLevel.value = tempRhythmLevel.value ?: 1 } - fun postToGetRhythmUrlFromServer(level: Int) { + fun postToGetRhythmUrlFromServer() { _rhythmUrlState.value = UiState.Loading viewModelScope.launch { rhythmRepository.postToGetRhythmUrl(bpm) @@ -142,6 +142,8 @@ constructor( _lastStepTime.value = 0L } + fun getBpmFromDataStore() = userRepository.getBpm() + private fun setBpm(level: Int) = 40 + level * 10 private fun setBpmLevel(bpm: Int) = diff --git a/presentation/src/main/java/com/kkkk/presentation/manager/WearableDataManager.kt b/presentation/src/main/java/com/kkkk/presentation/manager/WearableDataManager.kt new file mode 100644 index 0000000..2c4129b --- /dev/null +++ b/presentation/src/main/java/com/kkkk/presentation/manager/WearableDataManager.kt @@ -0,0 +1,38 @@ +package com.kkkk.presentation.manager + +import com.google.android.gms.tasks.Task +import com.google.android.gms.wearable.DataClient +import com.google.android.gms.wearable.DataItem +import com.google.android.gms.wearable.PutDataMapRequest +import com.google.android.gms.wearable.PutDataRequest +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WearableDataManager @Inject constructor( + private val dataClient: DataClient +) { + + fun sendIntToWearable(path: String, key: String, value: Int): Task { + + Timber.tag("okhttp").d("START SENDING DATA TO WEARABLE") + + val putDataReq: PutDataRequest = PutDataMapRequest.create(path).run { + dataMap.putInt(key, value) + asPutDataRequest().setUrgent() + } + + return dataClient.putDataItem(putDataReq).addOnSuccessListener { dataItem -> + Timber.tag("okhttp").d("SEND DATA TO WEARABLE SUCCESS : ${dataItem.uri}") + }.addOnFailureListener { exception -> + Timber.tag("okhttp").d("SEND DATA TO WEARABLE FAIL : ${exception.message}") + } + } + + companion object { + const val KEY_BPM = "KEY_BPM" + + const val PATH_BPM = "/bpm" + } +} \ No newline at end of file diff --git a/stempo/build.gradle.kts b/stempo/build.gradle.kts index acc561d..94a3fa0 100644 --- a/stempo/build.gradle.kts +++ b/stempo/build.gradle.kts @@ -69,10 +69,15 @@ dependencies { AndroidXDependencies.run { implementation(hilt) implementation(hiltWorkManager) + implementation(wearable) } KaptDependencies.run { kapt(hiltCompiler) kapt(hiltWorkManagerCompiler) } + + ThirdPartyDependencies.run { + implementation(timber) + } } \ No newline at end of file diff --git a/stempo/src/main/AndroidManifest.xml b/stempo/src/main/AndroidManifest.xml index ecfe4df..3b015af 100644 --- a/stempo/src/main/AndroidManifest.xml +++ b/stempo/src/main/AndroidManifest.xml @@ -15,14 +15,11 @@ android:supportsRtl="true" android:theme="@android:style/Theme.DeviceDefault" android:usesCleartextTraffic="true"> + - @@ -39,6 +36,7 @@ + \ No newline at end of file diff --git a/stempo/src/main/java/com/kkkk/stempo/presentation/MyApp.kt b/stempo/src/main/java/com/kkkk/stempo/presentation/MyApp.kt index 3515f0e..784c9ce 100644 --- a/stempo/src/main/java/com/kkkk/stempo/presentation/MyApp.kt +++ b/stempo/src/main/java/com/kkkk/stempo/presentation/MyApp.kt @@ -1,11 +1,19 @@ package com.kkkk.stempo.presentation import android.app.Application +import com.kkkk.di.BuildConfig import dagger.hilt.android.HiltAndroidApp +import timber.log.Timber @HiltAndroidApp class MyApp : Application() { override fun onCreate() { super.onCreate() + + initTimber() + } + + private fun initTimber() { + if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) } } \ No newline at end of file diff --git a/stempo/src/main/java/com/kkkk/stempo/presentation/WatchActivity.kt b/stempo/src/main/java/com/kkkk/stempo/presentation/WatchActivity.kt index ced987f..4d3f401 100644 --- a/stempo/src/main/java/com/kkkk/stempo/presentation/WatchActivity.kt +++ b/stempo/src/main/java/com/kkkk/stempo/presentation/WatchActivity.kt @@ -10,12 +10,18 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import com.google.android.gms.wearable.DataClient +import com.google.android.gms.wearable.DataEvent +import com.google.android.gms.wearable.DataEventBuffer +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.theme.StempoandroidTheme import dagger.hilt.android.AndroidEntryPoint +import timber.log.Timber @AndroidEntryPoint -class WatchActivity : ComponentActivity() { +class WatchActivity : ComponentActivity(), DataClient.OnDataChangedListener { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -29,4 +35,38 @@ class WatchActivity : ComponentActivity() { } } } + + override fun onResume() { + super.onResume() + Timber.tag("okhttp").d("LISTENER : ADDED") + Wearable.getDataClient(this).addListener(this) + } + + override fun onPause() { + super.onPause() + Wearable.getDataClient(this).removeListener(this) + } + + override fun onDataChanged(dataEvents: DataEventBuffer) { + Timber.tag("okhttp").d("LISTENER : ON DATA CHANGED") + + dataEvents.forEach { event -> + if (event.type == DataEvent.TYPE_CHANGED) { + event.dataItem.also { item -> + if (item.uri.path?.compareTo(PATH_BPM) == 0) { + DataMapItem.fromDataItem(item).dataMap.apply { + val bpm = getInt(KEY_BPM) + Timber.tag("okhttp").d("LISTENER : DATA RECEIVED : $bpm") + } + } + } + } + } + } + + companion object { + const val KEY_BPM = "KEY_BPM" + + const val PATH_BPM = "/bpm" + } }