A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(wear): Add navigation layer

This is the last big commit for creating the initial, guiding
project structure.

Replaces the monolithic `MainActivity` with a variety of
narrowly focused Composables which assemble nicely into
a full application.

In particular, this commit shows off the ability to press an activity
which opens a new screen with chips for that activity's tags.

A major milestone towards #5

Joseph Hale (Feb 26, 2024, 10:23 PM -0700) 65d3fe67 c69bc685

+571 -140
+6 -1
wear/build.gradle.kts
··· 58 58 } 59 59 60 60 dependencies { 61 + 61 62 var compose_version = "1.3.1" 62 63 var wear_compose_version = "1.1.0" 63 - // Custom Dependencies 64 + // Runtime Dependencies 65 + implementation("androidx.wear.compose:compose-navigation:$wear_compose_version") 64 66 implementation("com.google.android.horologist:horologist-compose-layout:0.2.7") 65 67 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") 66 68 implementation("androidx.compose.material:material-icons-core:1.6.1") 67 69 implementation(project(":wearrpc")) 70 + 71 + // Dev Dependencies 72 + implementation("androidx.wear:wear-tooling-preview:1.0.0") 68 73 69 74 // Default Dependencies 70 75 implementation("androidx.core:core-ktx:1.7.0")
+2 -139
wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt
··· 6 6 package com.example.util.simpletimetracker.presentation 7 7 8 8 import android.os.Bundle 9 - import android.widget.Toast 10 9 import androidx.activity.ComponentActivity 11 10 import androidx.activity.compose.setContent 12 - import androidx.compose.foundation.background 13 - import androidx.compose.foundation.layout.Arrangement 14 - import androidx.compose.foundation.layout.PaddingValues 15 - import androidx.compose.foundation.layout.fillMaxSize 16 - import androidx.compose.foundation.layout.padding 17 - import androidx.compose.foundation.selection.selectableGroup 18 - import androidx.compose.material.icons.Icons 19 - import androidx.compose.material.icons.rounded.Refresh 20 11 import androidx.compose.runtime.Composable 21 - import androidx.compose.runtime.LaunchedEffect 22 - import androidx.compose.runtime.getValue 23 - import androidx.compose.runtime.mutableIntStateOf 24 - import androidx.compose.runtime.mutableStateOf 25 - import androidx.compose.runtime.remember 26 - import androidx.compose.runtime.rememberCoroutineScope 27 - import androidx.compose.runtime.setValue 28 - import androidx.compose.ui.Modifier 29 - import androidx.compose.ui.platform.LocalContext 30 - import androidx.compose.ui.unit.dp 31 - import androidx.wear.compose.material.Icon 32 - import androidx.wear.compose.material.MaterialTheme 33 - import androidx.wear.compose.material.OutlinedButton 34 - import androidx.wear.compose.material.PositionIndicator 35 - import androidx.wear.compose.material.Scaffold 36 - import androidx.wear.compose.material.ScalingLazyColumn 37 - import androidx.wear.compose.material.ScalingLazyListState 38 - import androidx.wear.compose.material.TimeText 39 - import androidx.wear.compose.material.Vignette 40 - import androidx.wear.compose.material.VignettePosition 41 - import androidx.wear.compose.material.rememberScalingLazyListState 42 - import androidx.wear.compose.material.scrollAway 12 + import com.example.util.simpletimetracker.presentation.navigation.MainNavigator 43 13 import com.example.util.simpletimetracker.presentation.theme.SimpleTimeTrackerForWearOSTheme 44 - import com.example.util.simpletimetracker.wearrpc.Activity 45 - import com.example.util.simpletimetracker.wearrpc.ContextMessenger 46 - import com.example.util.simpletimetracker.wearrpc.CurrentActivity 47 - import com.example.util.simpletimetracker.wearrpc.WearRPCClient 48 - import com.google.android.horologist.compose.focus.rememberActiveFocusRequester 49 - import com.google.android.horologist.compose.navscaffold.ExperimentalHorologistComposeLayoutApi 50 - import com.google.android.horologist.compose.rotaryinput.rememberRotaryHapticFeedback 51 - import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll 52 - import kotlinx.coroutines.Dispatchers 53 - import kotlinx.coroutines.async 54 - import kotlinx.coroutines.launch 55 14 56 15 class MainActivity : ComponentActivity() { 57 16 override fun onCreate(savedInstanceState: Bundle?) { ··· 65 24 @Composable 66 25 fun WearApp() { 67 26 SimpleTimeTrackerForWearOSTheme { 68 - val scrollState = rememberScalingLazyListState() 69 - Scaffold( 70 - timeText = { 71 - TimeText(modifier = Modifier.scrollAway(scrollState)) 72 - }, 73 - vignette = { 74 - Vignette(vignettePosition = VignettePosition.TopAndBottom) 75 - }, 76 - positionIndicator = { 77 - PositionIndicator(scalingLazyListState = scrollState) 78 - }, 79 - ) { 80 - ActivityList(scrollState) 81 - } 27 + MainNavigator() 82 28 } 83 29 } 84 30 85 - 86 - @OptIn(ExperimentalHorologistComposeLayoutApi::class) 87 - @Composable 88 - fun ActivityList(scrollState: ScalingLazyListState = rememberScalingLazyListState()) { 89 - val context = LocalContext.current 90 - val composableScope = rememberCoroutineScope() 91 - val rpc = WearRPCClient(ContextMessenger(context)) 92 - 93 - var activities: Array<Activity> by remember { mutableStateOf(arrayOf()) } 94 - var activitiesQueryCount by remember { mutableIntStateOf(0) } 95 - LaunchedEffect( 96 - key1 = activitiesQueryCount, 97 - block = { 98 - async(Dispatchers.Default) { 99 - activities = rpc.queryActivities() 100 - } 101 - }, 102 - ) 103 - 104 - var currentActivities: Array<CurrentActivity> by remember { mutableStateOf(arrayOf()) } 105 - var currentActivitiesQueryCount by remember { mutableIntStateOf(0) } 106 - LaunchedEffect( 107 - key1 = activitiesQueryCount, 108 - block = { 109 - async(Dispatchers.Default) { 110 - currentActivities = rpc.queryCurrentActivities() 111 - } 112 - }, 113 - ) 114 - 115 - val focusRequester = rememberActiveFocusRequester() 116 - val rotaryHapticFeedback = rememberRotaryHapticFeedback() 117 - ScalingLazyColumn( 118 - modifier = Modifier 119 - .rotaryWithScroll(focusRequester, scrollState, rotaryHapticFeedback) 120 - .fillMaxSize() 121 - .background(MaterialTheme.colors.background) 122 - .selectableGroup(), 123 - contentPadding = PaddingValues(10.dp), 124 - verticalArrangement = Arrangement.Center, 125 - state = scrollState, 126 - ) { 127 - for (activity in activities) { 128 - val currentActivity = currentActivities.filter { it.id == activity.id }.getOrNull(0) 129 - item { 130 - ActivityChip( 131 - activity, 132 - startedAt = currentActivity?.startedAt, 133 - tags = currentActivity?.tags ?: arrayOf(), 134 - onClick = { 135 - composableScope.launch(Dispatchers.Default) { 136 - val tags = rpc.queryTagsForActivity(activity.id) 137 - composableScope.launch(Dispatchers.Main) { 138 - Toast.makeText( 139 - context, 140 - "`${activity.name}` (id: ${activity.id}) has tags [${ 141 - tags.joinToString(",") { it.name } 142 - }]", 143 - Toast.LENGTH_SHORT, 144 - ).show() 145 - } 146 - } 147 - }, 148 - ) 149 - } 150 - } 151 - item { 152 - OutlinedButton( 153 - onClick = { 154 - activitiesQueryCount++ 155 - currentActivitiesQueryCount++ 156 - Toast.makeText(context, "Refreshing activities", Toast.LENGTH_SHORT).show() 157 - }, 158 - content = { 159 - Icon(Icons.Rounded.Refresh, contentDescription = "Refresh Activities List") 160 - }, 161 - modifier = Modifier.padding(all = 8.dp), 162 - ) 163 - } 164 - } 165 - } 166 - 167 -
+16
wear/src/main/java/com/example/util/simpletimetracker/presentation/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Presentation 5 + 6 + The UI for the app. Responsible for everything the user sees and interacts with. 7 + 8 + ## MainActivity 9 + 10 + The `MainActivity` is what gets launched when the user opens the app on his/her 11 + smartwatch. The `MainActivity` typically immediately delegates to a `navigation` 12 + component which then manages which `screen` is first loaded and shown to the 13 + user. 14 + 15 + Each `screen` fetches the data it needs (typically using a `remember` function) 16 + then renders a `layout` containing one or more `components` for the user to interact with.
+61
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/ActivitiesList.kt
··· 1 + 2 + /* 3 + * This Source Code Form is subject to the terms of the Mozilla Public 4 + * License, v. 2.0. If a copy of the MPL was not distributed with this 5 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 + */ 7 + package com.example.util.simpletimetracker.presentation.components 8 + 9 + import androidx.compose.runtime.Composable 10 + import androidx.compose.ui.tooling.preview.Preview 11 + import androidx.wear.tooling.preview.devices.WearDevices 12 + import com.example.util.simpletimetracker.presentation.ActivityChip 13 + import com.example.util.simpletimetracker.presentation.layout.ScaffoldedScrollingColumn 14 + import com.example.util.simpletimetracker.wearrpc.Activity 15 + import com.example.util.simpletimetracker.wearrpc.CurrentActivity 16 + 17 + @Composable 18 + fun ActivitiesList( 19 + activities: Array<Activity>, 20 + currentActivities: Array<CurrentActivity>, 21 + onSelectActivity: (activity: Activity) -> Unit, 22 + onRefresh: () -> Unit, 23 + ) { 24 + ScaffoldedScrollingColumn { 25 + for (activity in activities) { 26 + val currentActivity = currentActivities.filter { it.id == activity.id }.getOrNull(0) 27 + item { 28 + ActivityChip( 29 + activity, 30 + startedAt = currentActivity?.startedAt, 31 + tags = currentActivity?.tags ?: arrayOf(), 32 + onClick = { onSelectActivity(activity) }, 33 + ) 34 + } 35 + } 36 + item { 37 + RefreshButton( 38 + onClick = onRefresh, 39 + contentDescription = "Refresh Activities List", 40 + ) 41 + } 42 + } 43 + } 44 + 45 + @Preview(device = WearDevices.LARGE_ROUND) 46 + @Composable 47 + private fun Preview() { 48 + val activities = arrayOf( 49 + Activity(1234, "Chores", "🧹", "#FA0000"), 50 + Activity(4321, "Sleep", "🛏️", "#0000FA"), 51 + ) 52 + val currents = arrayOf( 53 + CurrentActivity(id = 4321, startedAt = 1708241427000L, tags = arrayOf()) 54 + ) 55 + ActivitiesList( 56 + activities, 57 + currentActivities = currents, 58 + onSelectActivity = { /* `it` is the selected activity */ }, 59 + onRefresh = { /* What to do when requesting a refresh */ } 60 + ) 61 + }
+13
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Components 5 + 6 + Components are "view-only" chunks of UI. In other words, the UI they render 7 + depends only on the values of the parameters passed to them -- no reading data 8 + from any sort of state, local or remote. 9 + 10 + These properties make components extraordinarily easy to test, particularly by 11 + adding `@Preview` versions within the source code file. 12 + - Note `@Preview` versions also render *immediately* in the preview pane, 13 + dramatically improving development velocity.
+44
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/RefreshButton.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.components 7 + 8 + import android.widget.Toast 9 + import androidx.compose.foundation.layout.padding 10 + import androidx.compose.material.icons.Icons 11 + import androidx.compose.material.icons.rounded.Refresh 12 + import androidx.compose.runtime.Composable 13 + import androidx.compose.ui.Modifier 14 + import androidx.compose.ui.platform.LocalContext 15 + import androidx.compose.ui.tooling.preview.Preview 16 + import androidx.compose.ui.unit.dp 17 + import androidx.wear.compose.material.Icon 18 + import androidx.wear.compose.material.OutlinedButton 19 + 20 + @Composable 21 + fun RefreshButton(onClick: () -> Unit, contentDescription: String) { 22 + val context = LocalContext.current 23 + 24 + OutlinedButton( 25 + onClick = { 26 + // Idea: Instead of a toast, spin the button while the callback is executing... 27 + Toast.makeText(context, "Refreshing", Toast.LENGTH_SHORT).show() 28 + onClick() 29 + }, 30 + content = { 31 + Icon(Icons.Rounded.Refresh, contentDescription = contentDescription) 32 + }, 33 + modifier = Modifier.padding(all = 8.dp), 34 + ) 35 + } 36 + 37 + @Preview 38 + @Composable 39 + private fun Preview() { 40 + RefreshButton( 41 + onClick = { /* Log.i("Preview", "Refresh Button clicked!") */ }, 42 + contentDescription = "Refreshing...", 43 + ) 44 + }
+20
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagChip.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.components 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.ui.text.style.TextOverflow 10 + import androidx.wear.compose.material.Chip 11 + import androidx.wear.compose.material.Text 12 + import com.example.util.simpletimetracker.wearrpc.Tag 13 + 14 + @Composable 15 + fun TagChip(tag: Tag, onClick: () -> Unit) { 16 + Chip( 17 + onClick = onClick, 18 + label = { Text(tag.name, maxLines = 1, overflow = TextOverflow.Ellipsis) }, 19 + ) 20 + }
+21
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagList.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.components 7 + 8 + import androidx.compose.runtime.Composable 9 + import com.example.util.simpletimetracker.presentation.layout.ScaffoldedScrollingColumn 10 + import com.example.util.simpletimetracker.wearrpc.Tag 11 + 12 + @Composable 13 + fun TagList(tags: Array<Tag>, onSelectTag: (tag: Tag) -> Unit) { 14 + ScaffoldedScrollingColumn { 15 + for (tag in tags) { 16 + item { 17 + TagChip(tag) { onSelectTag(tag) } 18 + } 19 + } 20 + } 21 + }
+9
wear/src/main/java/com/example/util/simpletimetracker/presentation/layout/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Layout 5 + 6 + Structural composables which organize their content composables for painting on 7 + the screen. 8 + 9 + Typically focus on how to position multiple components relative to each other.
+18
wear/src/main/java/com/example/util/simpletimetracker/presentation/layout/ScaffoldedScrollingColumn.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.layout 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.wear.compose.material.ScalingLazyListScope 10 + import androidx.wear.compose.material.rememberScalingLazyListState 11 + 12 + @Composable 13 + fun ScaffoldedScrollingColumn(content: ScalingLazyListScope.() -> Unit) { 14 + val scrollState = rememberScalingLazyListState() 15 + Scaffolding(scrollState) { 16 + ScrollingColumn(scrollState, content) 17 + } 18 + }
+36
wear/src/main/java/com/example/util/simpletimetracker/presentation/layout/Scaffolding.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.layout 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.ui.Modifier 10 + import androidx.wear.compose.material.PositionIndicator 11 + import androidx.wear.compose.material.Scaffold 12 + import androidx.wear.compose.material.ScalingLazyListState 13 + import androidx.wear.compose.material.TimeText 14 + import androidx.wear.compose.material.Vignette 15 + import androidx.wear.compose.material.VignettePosition 16 + import androidx.wear.compose.material.rememberScalingLazyListState 17 + import androidx.wear.compose.material.scrollAway 18 + 19 + @Composable 20 + fun Scaffolding( 21 + scrollState: ScalingLazyListState = rememberScalingLazyListState(), 22 + content: @Composable () -> Unit, 23 + ) { 24 + Scaffold( 25 + timeText = { 26 + TimeText(modifier = Modifier.scrollAway(scrollState)) 27 + }, 28 + vignette = { 29 + Vignette(vignettePosition = VignettePosition.TopAndBottom) 30 + }, 31 + positionIndicator = { 32 + PositionIndicator(scalingLazyListState = scrollState) 33 + }, 34 + content = content, 35 + ) 36 + }
+45
wear/src/main/java/com/example/util/simpletimetracker/presentation/layout/ScrollingColumn.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.layout 7 + 8 + import androidx.compose.foundation.background 9 + import androidx.compose.foundation.layout.Arrangement 10 + import androidx.compose.foundation.layout.PaddingValues 11 + import androidx.compose.foundation.layout.fillMaxSize 12 + import androidx.compose.foundation.selection.selectableGroup 13 + import androidx.compose.runtime.Composable 14 + import androidx.compose.ui.Modifier 15 + import androidx.compose.ui.unit.dp 16 + import androidx.wear.compose.material.MaterialTheme 17 + import androidx.wear.compose.material.ScalingLazyColumn 18 + import androidx.wear.compose.material.ScalingLazyListScope 19 + import androidx.wear.compose.material.ScalingLazyListState 20 + import androidx.wear.compose.material.rememberScalingLazyListState 21 + import com.google.android.horologist.compose.focus.rememberActiveFocusRequester 22 + import com.google.android.horologist.compose.navscaffold.ExperimentalHorologistComposeLayoutApi 23 + import com.google.android.horologist.compose.rotaryinput.rememberRotaryHapticFeedback 24 + import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll 25 + 26 + @OptIn(ExperimentalHorologistComposeLayoutApi::class) 27 + @Composable 28 + fun ScrollingColumn( 29 + scrollState: ScalingLazyListState = rememberScalingLazyListState(), 30 + content: ScalingLazyListScope.() -> Unit, 31 + ) { 32 + val focusRequester = rememberActiveFocusRequester() 33 + val rotaryHapticFeedback = rememberRotaryHapticFeedback() 34 + ScalingLazyColumn( 35 + modifier = Modifier 36 + .rotaryWithScroll(focusRequester, scrollState, rotaryHapticFeedback) 37 + .fillMaxSize() 38 + .background(MaterialTheme.colors.background) 39 + .selectableGroup(), 40 + contentPadding = PaddingValues(10.dp), 41 + verticalArrangement = Arrangement.Center, 42 + state = scrollState, 43 + content = content, 44 + ) 45 + }
+34
wear/src/main/java/com/example/util/simpletimetracker/presentation/navigation/MainNavigator.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.navigation 7 + 8 + import android.util.Log 9 + import androidx.compose.runtime.Composable 10 + import androidx.wear.compose.navigation.SwipeDismissableNavHost 11 + import androidx.wear.compose.navigation.composable 12 + import androidx.wear.compose.navigation.rememberSwipeDismissableNavController 13 + import com.example.util.simpletimetracker.presentation.screens.ActivitiesScreen 14 + import com.example.util.simpletimetracker.presentation.screens.TagsScreen 15 + 16 + @Composable 17 + fun MainNavigator() { 18 + val navController = rememberSwipeDismissableNavController() 19 + SwipeDismissableNavHost( 20 + navController = navController, 21 + startDestination = "activities" 22 + ) { 23 + composable("activities") { 24 + ActivitiesScreen(onSelectActivity = { id -> 25 + navController.navigate("activities/$id/tags") 26 + }) 27 + } 28 + composable("activities/{id}/tags") { 29 + TagsScreen(activityId = it.arguments?.getString("id")?.toLong()!!, onSelectTag = { 30 + navController.navigate("activities") 31 + }) 32 + } 33 + } 34 + }
+10
wear/src/main/java/com/example/util/simpletimetracker/presentation/navigation/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Navigation 5 + 6 + Navigation is responsible for moving users between screens of the app. 7 + 8 + Typically, each screen will take one or more listener lambdas as parameters, to 9 + which Navigation will provide implementations which move the user from one 10 + screen to another.
+16
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Remember Functions 5 + 6 + A package of `remember` functions for usage in other Composables. 7 + 8 + > If you have worked with ReactJS, this package will remind you of 9 + [React Hooks](https://react.dev/reference/react/hooks). 10 + 11 + Essentially, these `remember` functions are reusable chunks of logic that can be 12 + plugged into and shared between multiple Composables. 13 + 14 + They contain zero UI/rendering code. Rather they focus on wrapping reasonably 15 + complex chunks of logic (e.g. fetching data from a remote source) for easy 16 + consumption by a UI element.
+50
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/activities.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.remember 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.runtime.LaunchedEffect 10 + import androidx.compose.runtime.getValue 11 + import androidx.compose.runtime.mutableIntStateOf 12 + import androidx.compose.runtime.mutableStateOf 13 + import androidx.compose.runtime.remember 14 + import androidx.compose.runtime.setValue 15 + import com.example.util.simpletimetracker.wearrpc.Activity 16 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 17 + import kotlinx.coroutines.Dispatchers 18 + import kotlinx.coroutines.async 19 + 20 + /** 21 + * Handles asynchronous retrieval of the available activities in Simple Time Tracker on the 22 + * connected phone. 23 + * 24 + * Usage: 25 + * ``` 26 + * val rpc = /* create a WearRPCClient instance */ 27 + * val (activities, refresh) = rememberActivities(rpc) 28 + * ``` 29 + * 30 + * Initially, `activities` will be an empty array. Once the actual activities are received 31 + * from the phone, `activities` will *automatically* update to that array and the encapsulating 32 + * Composable will *automatically* re-render. 33 + * 34 + * `refresh` is a function you can call to forcibly re-request the array of available activities 35 + * from the phone. 36 + */ 37 + @Composable 38 + fun rememberActivities(rpc: WearRPCClient): Pair<Array<Activity>, () -> Unit> { 39 + var activities: Array<Activity> by remember { mutableStateOf(arrayOf()) } 40 + var activitiesQueryCount by remember { mutableIntStateOf(0) } 41 + LaunchedEffect( 42 + key1 = activitiesQueryCount, 43 + block = { 44 + async(Dispatchers.Default) { 45 + activities = rpc.queryActivities() 46 + } 47 + }, 48 + ) 49 + return Pair(activities) { activitiesQueryCount++ } 50 + }
+50
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/currentActivities.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.remember 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.runtime.LaunchedEffect 10 + import androidx.compose.runtime.getValue 11 + import androidx.compose.runtime.mutableIntStateOf 12 + import androidx.compose.runtime.mutableStateOf 13 + import androidx.compose.runtime.remember 14 + import androidx.compose.runtime.setValue 15 + import com.example.util.simpletimetracker.wearrpc.CurrentActivity 16 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 17 + import kotlinx.coroutines.Dispatchers 18 + import kotlinx.coroutines.async 19 + 20 + /** 21 + * Handles asynchronous retrieval of the currently running activities in Simple Time Tracker on the 22 + * connected phone. 23 + * 24 + * Usage: 25 + * ``` 26 + * val rpc = /* create a WearRPCClient instance */ 27 + * val (currents, refresh) = rememberCurrentActivities(rpc) 28 + * ``` 29 + * 30 + * Initially, `currents` will be an empty array. Once the actual current activities are received 31 + * from the phone, `currents` will *automatically* update to that array and the encapsulating 32 + * Composable will *automatically* re-render. 33 + * 34 + * `refresh` is a function you can call to forcibly re-request the array of current activities 35 + * from the phone. 36 + */ 37 + @Composable 38 + fun rememberCurrentActivities(rpc: WearRPCClient): Pair<Array<CurrentActivity>, () -> Unit> { 39 + var currentActivities: Array<CurrentActivity> by remember { mutableStateOf(arrayOf()) } 40 + var currentActivitiesQueryCount by remember { mutableIntStateOf(0) } 41 + LaunchedEffect( 42 + key1 = currentActivitiesQueryCount, 43 + block = { 44 + async(Dispatchers.Default) { 45 + currentActivities = rpc.queryCurrentActivities() 46 + } 47 + }, 48 + ) 49 + return Pair(currentActivities) { currentActivitiesQueryCount++ } 50 + }
+51
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/tags.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.remember 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.runtime.LaunchedEffect 10 + import androidx.compose.runtime.getValue 11 + import androidx.compose.runtime.mutableIntStateOf 12 + import androidx.compose.runtime.mutableStateOf 13 + import androidx.compose.runtime.remember 14 + import androidx.compose.runtime.setValue 15 + import com.example.util.simpletimetracker.wearrpc.Tag 16 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 17 + import kotlinx.coroutines.Dispatchers 18 + import kotlinx.coroutines.async 19 + 20 + /** 21 + * Handles asynchronous retrieval of the available tags for a specific activity in 22 + * Simple Time Tracker on the connected phone. 23 + * 24 + * Usage: 25 + * ``` 26 + * val activityId: Long = /* obtain the ID (e.g. as a parameter) */ 27 + * val rpc = /* create a WearRPCClient instance */ 28 + * val (tags, refresh) = rememberTags(rpc, activityId) 29 + * ``` 30 + * 31 + * Initially, `tags` will be an empty array. Once the actual tags are received 32 + * from the phone, `tags` will *automatically* update to that array and the encapsulating 33 + * Composable will *automatically* re-render. 34 + * 35 + * `refresh` is a function you can call to forcibly re-request the array of available tags 36 + * from the phone. 37 + */ 38 + @Composable 39 + fun rememberTags(rpc: WearRPCClient, activityId: Long): Pair<Array<Tag>, () -> Unit> { 40 + var tags: Array<Tag> by remember { mutableStateOf(arrayOf()) } 41 + var tagsQueryCount by remember { mutableIntStateOf(0) } 42 + LaunchedEffect( 43 + key1 = tagsQueryCount, 44 + block = { 45 + async(Dispatchers.Default) { 46 + tags = rpc.queryTagsForActivity(activityId) 47 + } 48 + }, 49 + ) 50 + return Pair(tags) { tagsQueryCount++ } 51 + }
+37
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/ActivitiesScreen.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.screens 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.ui.platform.LocalContext 10 + import com.example.util.simpletimetracker.presentation.components.ActivitiesList 11 + import com.example.util.simpletimetracker.presentation.remember.rememberActivities 12 + import com.example.util.simpletimetracker.presentation.remember.rememberCurrentActivities 13 + import com.example.util.simpletimetracker.wearrpc.ContextMessenger 14 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 15 + 16 + 17 + @Composable 18 + fun ActivitiesScreen(onSelectActivity: (activityId: Long) -> Unit) { 19 + val rpc = WearRPCClient(ContextMessenger(LocalContext.current)) 20 + val (activities, refreshActivities) = rememberActivities(rpc) 21 + val (currentActivities, refreshCurrentActivities) = rememberCurrentActivities(rpc) 22 + 23 + ActivitiesList( 24 + activities, 25 + currentActivities, 26 + onSelectActivity = { 27 + onSelectActivity(it.id) 28 + }, 29 + onRefresh = { 30 + refreshActivities() 31 + refreshCurrentActivities() 32 + }, 33 + ) 34 + } 35 + 36 + 37 +
+11
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/README.md
··· 1 + <!-- This Source Code Form is subject to the terms of the Mozilla Public 2 + - License, v. 2.0. If a copy of the MPL was not distributed with this 3 + - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> 4 + # Screens 5 + 6 + Full-page, interactive user-facing experiences. 7 + 8 + Screens are typically assemble one or more component composables into a UI that 9 + a user finds useful. 10 + - Fetch relevant data, which is then passed to a component for rendering. 11 + - Configure listeners and reactions to button taps/events.
+21
wear/src/main/java/com/example/util/simpletimetracker/presentation/screens/TagsScreen.kt
··· 1 + /* 2 + * This Source Code Form is subject to the terms of the Mozilla Public 3 + * License, v. 2.0. If a copy of the MPL was not distributed with this 4 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 + */ 6 + package com.example.util.simpletimetracker.presentation.screens 7 + 8 + import androidx.compose.runtime.Composable 9 + import androidx.compose.ui.platform.LocalContext 10 + import com.example.util.simpletimetracker.presentation.components.TagList 11 + import com.example.util.simpletimetracker.presentation.remember.rememberTags 12 + import com.example.util.simpletimetracker.wearrpc.ContextMessenger 13 + import com.example.util.simpletimetracker.wearrpc.WearRPCClient 14 + 15 + @Composable 16 + fun TagsScreen(activityId: Long, onSelectTag: () -> Unit) { 17 + val rpc = WearRPCClient(ContextMessenger(LocalContext.current)) 18 + val (tags) = rememberTags(rpc, activityId) 19 + 20 + TagList(tags, onSelectTag = { onSelectTag() } ) 21 + }