diff --git a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt
index 0afccdfb..9b37be5c 100644
--- a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt
+++ b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Deps.kt
@@ -83,6 +83,8 @@ object Deps {
"androidx.wear:wear-tooling-preview:${Versions.wear_compose_tooling_preview}"
const val horologist =
"com.google.android.horologist:horologist-compose-layout:${Versions.horologist}"
+ const val hilt =
+ "androidx.hilt:hilt-navigation-compose:${Versions.compose_hilt}"
const val uiTooling =
"androidx.compose.ui:ui-tooling:${Versions.compose_version}"
}
diff --git a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt
index 7541865a..831a1ab1 100644
--- a/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt
+++ b/buildSrc/src/main/kotlin/com/example/util/simpletimetracker/Versions.kt
@@ -36,6 +36,7 @@ object Versions {
const val wear_compose_tooling_preview = "1.0.0"
const val horologist = "0.2.7"
const val compose_kotlin_compiler = "1.4.0-alpha02"
+ const val compose_hilt = "1.0.0"
const val junit = "4.13"
const val junitUi = "1.1.4"
diff --git a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearModule.kt
similarity index 96%
rename from features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt
rename to features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearModule.kt
index 263f7be7..95b62496 100644
--- a/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WidgetModule.kt
+++ b/features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearModule.kt
@@ -13,7 +13,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
-interface WidgetModule {
+interface WearModule {
@Binds
fun WearCommunicationInteractor.bindWearCommunicationInteractor(): WearCommunicationAPI
diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts
index 0a677d81..f77e9814 100644
--- a/wear/build.gradle.kts
+++ b/wear/build.gradle.kts
@@ -70,6 +70,7 @@ dependencies {
implementation(Deps.Compose.wearFoundation)
implementation(Deps.Compose.wearToolingPreview)
implementation(Deps.Compose.horologist)
+ implementation(Deps.Compose.hilt)
debugImplementation(Deps.Compose.uiTooling)
kapt(Deps.Kapt.dagger)
diff --git a/wear/src/main/AndroidManifest.xml b/wear/src/main/AndroidManifest.xml
index 931b401d..c6d0410d 100644
--- a/wear/src/main/AndroidManifest.xml
+++ b/wear/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
()
val bestNode = findNearestNode(capability)
@@ -57,7 +63,7 @@ class ContextMessenger @Inject constructor(
deferred.cancel(CancellationException(logMessage))
}
- return deferred.await()
+ deferred.await()
}
private fun findNearestNode(capability: String): Node? {
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/di/WearModule.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/di/WearModule.kt
new file mode 100644
index 00000000..0805c423
--- /dev/null
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/di/WearModule.kt
@@ -0,0 +1,21 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package com.example.util.simpletimetracker.presentation.di
+
+import com.example.util.simpletimetracker.presentation.data.ContextMessenger
+import com.example.util.simpletimetracker.presentation.data.Messenger
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface WearModule {
+
+ @Binds
+ fun ContextMessenger.bindMessenger(): Messenger
+}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/CurrentActivitiesMediator.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/CurrentActivitiesMediator.kt
index 0860b542..21809f73 100644
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/CurrentActivitiesMediator.kt
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/CurrentActivitiesMediator.kt
@@ -1,24 +1,26 @@
package com.example.util.simpletimetracker.presentation.mediators
+import com.example.util.simpletimetracker.presentation.data.WearRPCClient
import com.example.util.simpletimetracker.wear_api.WearCurrentActivity
import com.example.util.simpletimetracker.wear_api.WearSettings
import com.example.util.simpletimetracker.wear_api.WearTag
-import com.example.util.simpletimetracker.presentation.data.WearRPCClient
+import javax.inject.Inject
-class CurrentActivitiesMediator(
+class CurrentActivitiesMediator @Inject constructor(
private val rpc: WearRPCClient,
- private val currents: List,
) {
- suspend fun start(activityId: Long) {
- start(activityId, emptyList())
- }
- suspend fun start(activityId: Long, tags: List) {
+
+ suspend fun start(
+ activityId: Long,
+ tags: List = emptyList(),
+ ) {
val newCurrent = WearCurrentActivity(
id = activityId,
startedAt = System.currentTimeMillis(),
- tags = tags
+ tags = tags,
)
if (settings().allowMultitasking) {
+ val currents = rpc.queryCurrentActivities()
this.rpc.setCurrentActivities(currents.plus(newCurrent))
} else {
this.rpc.setCurrentActivities(listOf(newCurrent))
@@ -26,11 +28,12 @@ class CurrentActivitiesMediator(
}
suspend fun stop(currentId: Long) {
+ val currents = rpc.queryCurrentActivities()
val remaining = currents.filter { it.id != currentId }
this.rpc.setCurrentActivities(remaining)
}
+
private suspend fun settings(): WearSettings {
return this.rpc.querySettings()
}
-
}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/StartActivityMediator.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/StartActivityMediator.kt
index 158776c6..0c3e841c 100644
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/StartActivityMediator.kt
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/StartActivityMediator.kt
@@ -5,25 +5,39 @@
*/
package com.example.util.simpletimetracker.presentation.mediators
+import com.example.util.simpletimetracker.presentation.data.WearRPCClient
import com.example.util.simpletimetracker.wear_api.WearActivity
import com.example.util.simpletimetracker.wear_api.WearSettings
-import com.example.util.simpletimetracker.wear_api.WearCommunicationAPI
+import javax.inject.Inject
-class StartActivityMediator(
- private val api: WearCommunicationAPI,
- private val onRequestStartActivity: suspend (activity: WearActivity) -> Unit,
- private val onRequestTagSelection: suspend (activity: WearActivity) -> Unit,
+class StartActivityMediator @Inject constructor(
+ private val api: WearRPCClient,
) {
- suspend fun requestStart(activity: WearActivity) {
+
+ suspend fun requestStart(
+ activity: WearActivity,
+ onRequestStartActivity: suspend (activity: WearActivity) -> Unit,
+ onRequestTagSelection: suspend (activity: WearActivity) -> Unit,
+ ) {
val settings = api.querySettings()
if (settings.showRecordTagSelection) {
- requestTagSelectionIfNeeded(activity, settings)
+ requestTagSelectionIfNeeded(
+ activity = activity,
+ settings = settings,
+ onRequestStartActivity = onRequestStartActivity,
+ onRequestTagSelection = onRequestTagSelection,
+ )
} else {
onRequestStartActivity(activity)
}
}
- private suspend fun requestTagSelectionIfNeeded(activity: WearActivity, settings: WearSettings) {
+ private suspend fun requestTagSelectionIfNeeded(
+ activity: WearActivity,
+ settings: WearSettings,
+ onRequestStartActivity: suspend (activity: WearActivity) -> Unit,
+ onRequestTagSelection: suspend (activity: WearActivity) -> Unit,
+ ) {
val tags = api.queryTagsForActivity(activity.id)
val generalTags = tags.filter { it.isGeneral }
val nonGeneralTags = tags.filter { !it.isGeneral }
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/activities.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/activities.kt
deleted file mode 100644
index c918aa14..00000000
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/activities.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-package com.example.util.simpletimetracker.presentation.remember
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import com.example.util.simpletimetracker.wear_api.WearActivity
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-
-/**
- * Handles asynchronous retrieval of the available activities in Simple Time Tracker on the
- * connected phone.
- *
- * Usage:
- * ```
- * val (activities, refresh) = rememberActivities()
- * ```
- *
- * Initially, `activities` will be an empty array. Once the actual activities are received
- * from the phone, `activities` will *automatically* update to that array and the encapsulating
- * Composable will *automatically* re-render.
- *
- * `refresh` is a function you can call to forcibly re-request the array of available activities
- * from the phone.
- */
-@Composable
-fun rememberActivities(): Pair, () -> Unit> {
- val rpc = rememberRPCClient()
- var activities: List by remember { mutableStateOf(emptyList()) }
- var activitiesQueryCount by remember { mutableIntStateOf(0) }
- LaunchedEffect(activitiesQueryCount) {
- async(Dispatchers.Default) {
- activities = rpc.queryActivities()
- }
- }
- return Pair(activities) { activitiesQueryCount++ }
-}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/currentActivities.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/currentActivities.kt
deleted file mode 100644
index 7f2c2742..00000000
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/currentActivities.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-package com.example.util.simpletimetracker.presentation.remember
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import com.example.util.simpletimetracker.wear_api.WearCurrentActivity
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-
-/**
- * Handles asynchronous retrieval of the currently running activities in Simple Time Tracker on the
- * connected phone.
- *
- * Usage:
- * ```
- * val (currents, setCurrents, refresh) = rememberCurrentActivities()
- * ```
- *
- * Initially, `currents` will be an empty array. Once the actual current activities are received
- * from the phone, `currents` will *automatically* update to that array and the encapsulating
- * Composable will *automatically* re-render.
- *
- * `setCurrents` is a function you can call to set the array of current activities on the phone.
- *
- * `refresh` is a function you can call to forcibly re-request the array of current activities
- * from the phone.
- */
-@Composable
-fun rememberCurrentActivities(): Pair, () -> Unit> {
- val rpc = rememberRPCClient()
- var currentActivities: List by remember { mutableStateOf(emptyList()) }
- var currentActivitiesQueryCount by remember { mutableIntStateOf(0) }
- val queryCurrentActivities = { currentActivitiesQueryCount++ }
- LaunchedEffect(currentActivitiesQueryCount) {
- async(Dispatchers.Default) {
- currentActivities = rpc.queryCurrentActivities()
- }
- }
- return currentActivities to { queryCurrentActivities() }
-}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/rpcClient.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/rpcClient.kt
deleted file mode 100644
index f14ea04b..00000000
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/rpcClient.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.example.util.simpletimetracker.presentation.remember
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
-import com.example.util.simpletimetracker.presentation.data.ContextMessenger
-import com.example.util.simpletimetracker.presentation.data.WearRPCClient
-
-@Composable
-fun rememberRPCClient(): WearRPCClient {
- val context = LocalContext.current
- return WearRPCClient(ContextMessenger(context))
-}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/settings.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/settings.kt
deleted file mode 100644
index 3d67292f..00000000
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/settings.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-package com.example.util.simpletimetracker.presentation.remember
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import com.example.util.simpletimetracker.wear_api.WearSettings
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-
-/**
- * Handles asynchronous retrieval of the settings for Simple Time Tracker on the connected phone.
- *
- * Usage:
- * ```
- * val (settings, refresh) = rememberSettings()
- * ```
- *
- * Initially, `settings` will be `null`. Once the actual settings are received
- * from the phone, `settings` will *automatically* update to the object and the encapsulating
- * Composable will *automatically* re-render.
- *
- * `refresh` is a function you can call to forcibly re-request the settings from the phone.
- */
-@Composable
-fun rememberSettings(): Pair Unit> {
- var rpc = rememberRPCClient()
- var settings: WearSettings? by remember { mutableStateOf(null) }
- var settingsQueryCount by remember { mutableIntStateOf(0) }
- LaunchedEffect(settingsQueryCount) {
- async(Dispatchers.Default) {
- settings = rpc.querySettings()
- }
- }
- return Pair(settings) { settingsQueryCount++ }
-}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/tags.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/tags.kt
deleted file mode 100644
index 1b89a37d..00000000
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/tags.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-package com.example.util.simpletimetracker.presentation.remember
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import com.example.util.simpletimetracker.wear_api.WearTag
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-
-/**
- * Handles asynchronous retrieval of the available tags for a specific activity in
- * Simple Time Tracker on the connected phone.
- *
- * Usage:
- * ```
- * val activityId: Long = /* obtain the ID (e.g. as a parameter) */
- * val (tags, refresh) = rememberTags(activityId)
- * ```
- *
- * Initially, `tags` will be an empty array. Once the actual tags are received
- * from the phone, `tags` will *automatically* update to that array and the encapsulating
- * Composable will *automatically* re-render.
- *
- * `refresh` is a function you can call to forcibly re-request the array of available tags
- * from the phone.
- */
-@Composable
-fun rememberTags(activityId: Long): Pair, () -> Unit> {
- val rpc = rememberRPCClient()
- var tags: List by remember { mutableStateOf(emptyList()) }
- var tagsQueryCount by remember { mutableIntStateOf(0) }
- LaunchedEffect(tagsQueryCount) {
- async(Dispatchers.Default) {
- tags = rpc.queryTagsForActivity(activityId)
- }
- }
- return Pair(tags) { tagsQueryCount++ }
-}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesScreen.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesScreen.kt
index 068d26b9..0e2d014a 100644
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesScreen.kt
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesScreen.kt
@@ -5,73 +5,39 @@
*/
package com.example.util.simpletimetracker.presentation.screens
-import android.util.Log
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.hilt.navigation.compose.hiltViewModel
import com.example.util.simpletimetracker.presentation.components.ActivitiesList
import com.example.util.simpletimetracker.presentation.components.CreditsButton
-import com.example.util.simpletimetracker.presentation.mediators.CurrentActivitiesMediator
-import com.example.util.simpletimetracker.presentation.remember.rememberActivities
-import com.example.util.simpletimetracker.presentation.remember.rememberCurrentActivities
-import com.example.util.simpletimetracker.presentation.remember.rememberRPCClient
-import com.example.util.simpletimetracker.wear_api.WearActivity
-import com.example.util.simpletimetracker.presentation.mediators.StartActivityMediator
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
+import com.example.util.simpletimetracker.presentation.screens.ActivitiesViewModel.Effect
+import com.example.util.simpletimetracker.presentation.utils.collectEffects
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
@Composable
fun ActivitiesScreen(
onRequestTagSelection: (activityId: Long) -> Unit,
onRequestCredits: () -> Unit,
) {
- val coroutineScope = rememberCoroutineScope()
- val rpc = rememberRPCClient()
- val (activities, refreshActivities) = rememberActivities()
- val (currentActivities, refreshCurrentActivities) = rememberCurrentActivities()
- val refresh = {
- refreshActivities()
- refreshCurrentActivities()
- }
- val currentActivitiesMediator = CurrentActivitiesMediator(rpc, currentActivities)
- val startActivityWithoutTags: (WearActivity) -> Unit = {
- Log.d("ActivitiesScreen", "Starting ${it.name} (#${it.id}) without tags")
- coroutineScope.launch(Dispatchers.Default) {
- currentActivitiesMediator.start(it.id)
- refresh()
- }
- }
- val startActivityWithTags: (WearActivity) -> Unit = {
- coroutineScope.launch(Dispatchers.Main) {
- Log.d("ActivitiesScreen", "Starting ${it.name} (#${it.id}) with tags")
- onRequestTagSelection(it.id)
- }
- }
- val stopActivity: (WearActivity) -> Unit = {
- Log.d("ActivitiesScreen", "Stopping ${it.name} (#${it.id})")
- coroutineScope.launch(Dispatchers.Default) {
- currentActivitiesMediator.stop(it.id)
- refresh()
+ val viewModel = hiltViewModel()
+ viewModel.init()
+ val state = viewModel.state.collectAsState()
+
+ viewModel.effects.collectEffects(key = viewModel) {
+ when (it) {
+ is Effect.OnRequestTagSelection -> onRequestTagSelection(it.activityId)
}
}
- val startActivitiesMediator = StartActivityMediator(
- rpc,
- onRequestStartActivity = startActivityWithoutTags,
- onRequestTagSelection = startActivityWithTags,
- )
-
-
ActivitiesList(
- activities = activities,
- currentActivities = currentActivities,
- onSelectActivity = {
- coroutineScope.launch(Dispatchers.Default) {
- startActivitiesMediator.requestStart(it)
- }
- },
- onEnableActivity = startActivityWithoutTags,
- onDisableActivity = stopActivity,
- onRefresh = refresh,
+ activities = state.value.activities,
+ currentActivities = state.value.currentActivities,
+ onSelectActivity = viewModel::onSelectActivity,
+ onEnableActivity = viewModel::startActivityWithoutTags,
+ onDisableActivity = viewModel::stopActivity,
+ onRefresh = viewModel::refresh,
footer = { CreditsButton(onClick = onRequestCredits) },
)
}
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesViewModel.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesViewModel.kt
new file mode 100644
index 00000000..f108619f
--- /dev/null
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package com.example.util.simpletimetracker.presentation.screens
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.util.simpletimetracker.presentation.data.WearRPCClient
+import com.example.util.simpletimetracker.presentation.mediators.CurrentActivitiesMediator
+import com.example.util.simpletimetracker.presentation.mediators.StartActivityMediator
+import com.example.util.simpletimetracker.wear_api.WearActivity
+import com.example.util.simpletimetracker.wear_api.WearCurrentActivity
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ActivitiesViewModel @Inject constructor(
+ private val rpc: WearRPCClient,
+ private val startActivitiesMediator: StartActivityMediator,
+ private val currentActivitiesMediator: CurrentActivitiesMediator,
+) : ViewModel() {
+
+ val state = MutableStateFlow(State())
+ val effects = MutableSharedFlow(
+ extraBufferCapacity = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+
+ fun init() {
+ refresh()
+ }
+
+ fun startActivityWithoutTags(wearActivity: WearActivity) {
+ Log.d("ActivitiesScreen", "Starting ${wearActivity.name} (#${wearActivity.id}) without tags")
+ viewModelScope.launch {
+ currentActivitiesMediator.start(wearActivity.id)
+ refresh()
+ }
+ }
+
+ fun stopActivity(wearActivity: WearActivity) {
+ Log.d("ActivitiesScreen", "Stopping ${wearActivity.name} (#${wearActivity.id})")
+ viewModelScope.launch {
+ currentActivitiesMediator.stop(wearActivity.id)
+ refresh()
+ }
+ }
+
+ fun onSelectActivity(wearActivity: WearActivity) {
+ viewModelScope.launch {
+ startActivitiesMediator.requestStart(
+ activity = wearActivity,
+ onRequestStartActivity = ::startActivityWithoutTags,
+ onRequestTagSelection = ::startActivityWithTags,
+ )
+ }
+ }
+
+ fun refresh() {
+ viewModelScope.launch {
+ val newState = State(
+ activities = rpc.queryActivities(),
+ currentActivities = rpc.queryCurrentActivities(),
+ )
+ state.emit(newState)
+ }
+ }
+
+ private fun startActivityWithTags(wearActivity: WearActivity) {
+ viewModelScope.launch {
+ Log.d("ActivitiesScreen", "Starting ${wearActivity.name} (#${wearActivity.id}) with tags")
+ effects.emit(Effect.OnRequestTagSelection(wearActivity.id))
+ }
+ }
+
+ data class State(
+ val activities: List = emptyList(),
+ val currentActivities: List = emptyList(),
+ )
+
+ sealed interface Effect {
+ data class OnRequestTagSelection(val activityId: Long) : Effect
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt
index 59a9ead8..a4cfe382 100644
--- a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt
@@ -6,38 +6,33 @@
package com.example.util.simpletimetracker.presentation.screens
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.hilt.navigation.compose.hiltViewModel
import com.example.util.simpletimetracker.presentation.components.TagList
-import com.example.util.simpletimetracker.presentation.components.TagSelectionMode
-import com.example.util.simpletimetracker.presentation.mediators.CurrentActivitiesMediator
-import com.example.util.simpletimetracker.presentation.remember.rememberCurrentActivities
-import com.example.util.simpletimetracker.presentation.remember.rememberRPCClient
-import com.example.util.simpletimetracker.presentation.remember.rememberSettings
-import com.example.util.simpletimetracker.presentation.remember.rememberTags
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
+import com.example.util.simpletimetracker.presentation.screens.TagsViewModel.Effect
+import com.example.util.simpletimetracker.presentation.utils.collectEffects
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
@Composable
-fun TagsScreen(activityId: Long, onComplete: () -> Unit) {
- val coroutineScope = rememberCoroutineScope()
- val rpc = rememberRPCClient()
- val (settings) = rememberSettings()
- val (tags) = rememberTags(activityId)
- val (currentActivities) = rememberCurrentActivities()
- val currentActivitiesMediator = CurrentActivitiesMediator(rpc, currentActivities)
+fun TagsScreen(
+ activityId: Long,
+ onComplete: () -> Unit,
+) {
+ val viewModel = hiltViewModel()
+ viewModel.init(activityId)
+ val state = viewModel.state.collectAsState()
+
+ viewModel.effects.collectEffects(key = viewModel) {
+ when (it) {
+ is Effect.OnComplete -> onComplete()
+ }
+ }
TagList(
- tags,
- mode = if (settings?.recordTagSelectionCloseAfterOne != false) {
- TagSelectionMode.SINGLE
- } else {
- TagSelectionMode.MULTI
- },
- onSelectionComplete = {
- coroutineScope.launch(Dispatchers.Default) {
- currentActivitiesMediator.start(activityId, it)
- coroutineScope.launch(Dispatchers.Main) { onComplete() }
- }
- },
+ tags = state.value.tags,
+ mode = state.value.mode,
+ onSelectionComplete = { viewModel.onSelectionComplete(it) },
)
}
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsViewModel.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsViewModel.kt
new file mode 100644
index 00000000..139b7847
--- /dev/null
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsViewModel.kt
@@ -0,0 +1,80 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package com.example.util.simpletimetracker.presentation.screens
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.example.util.simpletimetracker.presentation.components.TagSelectionMode
+import com.example.util.simpletimetracker.presentation.data.WearRPCClient
+import com.example.util.simpletimetracker.presentation.mediators.CurrentActivitiesMediator
+import com.example.util.simpletimetracker.wear_api.WearSettings
+import com.example.util.simpletimetracker.wear_api.WearTag
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class TagsViewModel @Inject constructor(
+ private val rpc: WearRPCClient,
+ private val currentActivitiesMediator: CurrentActivitiesMediator,
+) : ViewModel() {
+
+ val state = MutableStateFlow(State())
+ val effects = MutableSharedFlow(
+ extraBufferCapacity = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
+
+ private var activityId: Long? = null
+
+ fun init(activityId: Long) {
+ this.activityId = activityId
+ refresh()
+ }
+
+ fun onSelectionComplete(tags: List) {
+ viewModelScope.launch {
+ val activityId = this@TagsViewModel.activityId ?: return@launch
+ currentActivitiesMediator.start(activityId, tags)
+ effects.emit(Effect.OnComplete)
+ }
+ }
+
+ private fun refresh() {
+ viewModelScope.launch {
+ val activityId = this@TagsViewModel.activityId ?: return@launch
+ val settings = rpc.querySettings()
+ val newState = State(
+ tags = rpc.queryTagsForActivity(activityId),
+ mode = if (settings.recordTagSelectionCloseAfterOne) {
+ TagSelectionMode.SINGLE
+ } else {
+ TagSelectionMode.MULTI
+ },
+ settings = settings,
+ )
+ state.emit(newState)
+ }
+ }
+
+ data class State(
+ val tags: List = emptyList(),
+ val mode: TagSelectionMode = TagSelectionMode.SINGLE,
+ val settings: WearSettings = WearSettings(
+ allowMultitasking = false,
+ showRecordTagSelection = false,
+ recordTagSelectionCloseAfterOne = false,
+ recordTagSelectionEvenForGeneralTags = false,
+ ),
+ )
+
+ sealed interface Effect {
+ object OnComplete : Effect
+ }
+}
\ No newline at end of file
diff --git a/wear/src/main/java/com/example/util/simpletimetracker/presentation/utils/Extensions.kt b/wear/src/main/java/com/example/util/simpletimetracker/presentation/utils/Extensions.kt
new file mode 100644
index 00000000..bc86f903
--- /dev/null
+++ b/wear/src/main/java/com/example/util/simpletimetracker/presentation/utils/Extensions.kt
@@ -0,0 +1,24 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package com.example.util.simpletimetracker.presentation.utils
+
+import android.annotation.SuppressLint
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
+
+@SuppressLint("ComposableNaming")
+@Composable
+fun Flow.collectEffects(
+ key: Any?,
+ action: (T) -> Unit,
+) {
+ LaunchedEffect(key) {
+ onEach { action(it) }.collect()
+ }
+}
\ No newline at end of file