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: Query tags for activity on Wear

Still just a proof of concept, but tapping an Activity Chip will now
create a Toast listing the tags available for the activity.

makes progress on thehale/SimpleTimeTracker-WearOS#4

Joseph Hale (Feb 26, 2024, 10:23 PM -0700) a7ca31fe a89bfb47

+45 -26
+14 -13
app/src/main/java/com/example/util/simpletimetracker/wear/DomainAPI.kt
··· 3 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 4 * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 5 */ 6 - package com.example.util.simpletimetracker.wear 6 + package com.example.util.simpletimetracker.wear 7 7 8 8 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 9 + import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 9 10 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 10 11 import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor 11 12 import com.example.util.simpletimetracker.domain.mapper.AppColorMapper ··· 15 16 import com.example.util.simpletimetracker.wearrpc.SimpleTimeTrackerAPI 16 17 import com.example.util.simpletimetracker.wearrpc.Tag 17 18 18 - class DomainAPI ( 19 + class DomainAPI( 19 20 private val prefsInteractor: PrefsInteractor, 20 21 private val recordTypeInteractor: RecordTypeInteractor, 22 + private val recordTagInteractor: RecordTagInteractor, 21 23 private val runningRecordInteractor: RunningRecordInteractor, 22 24 private val appColorMapper: AppColorMapper, 23 - ): SimpleTimeTrackerAPI { 25 + ) : SimpleTimeTrackerAPI { 24 26 25 27 override suspend fun queryActivities(): Array<Activity> { 26 - return recordTypeInteractor.getAll() 27 - .filter { recordType -> !recordType.hidden } 28 + return recordTypeInteractor.getAll().filter { recordType -> !recordType.hidden } 28 29 .map { recordType -> 29 30 val color = appColorMapper.mapToColorInt(recordType.color) 30 31 val hex = String.format("#%06X", (0xFFFFFF and color)) 31 32 Activity(recordType.id, recordType.name, recordType.icon, hex) 32 - } 33 - .toTypedArray() 33 + }.toTypedArray() 34 34 } 35 35 36 36 override suspend fun queryCurrentActivities(): Array<CurrentActivity> { ··· 38 38 CurrentActivity( 39 39 it.id, 40 40 it.timeStarted, 41 - arrayOf() // TODO - Pull actual list of active tags 41 + arrayOf(), // TODO - Pull actual list of active tags 42 42 ) 43 43 }.toTypedArray() 44 44 } ··· 54 54 // Activities in the given Array that are not running should be started 55 55 56 56 // For activities in the given Array which are running... 57 - // If the start dates + tags are unchanged, then leave the activity running. 58 - // If the start dates and/or tags are different, stop the current running activity 59 - // instance and restart it as of the requested start date. 57 + // If the start dates + tags are unchanged, then leave the activity running. 58 + // If the start dates and/or tags are different, stop the current running activity 59 + // instance and restart it as of the requested start date. 60 60 61 61 } 62 62 63 63 override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 64 - TODO("Not yet implemented") 65 - // Look up the tags which can be associated with this activity and return them 64 + return recordTagInteractor.getByTypeOrUntyped(activityId).filter { !it.archived }.map { 65 + Tag(id = it.id, name = it.name) 66 + }.toTypedArray() 66 67 } 67 68 68 69 override suspend fun querySettings(): Settings {
+5
app/src/main/java/com/example/util/simpletimetracker/wear/WearService.kt
··· 6 6 package com.example.util.simpletimetracker.wear 7 7 8 8 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 9 + import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 9 10 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor 10 11 import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor 11 12 import com.example.util.simpletimetracker.domain.mapper.AppColorMapper ··· 33 34 lateinit var recordTypeInteractor: RecordTypeInteractor 34 35 35 36 @Inject 37 + lateinit var recordTagInteractor: RecordTagInteractor 38 + 39 + @Inject 36 40 lateinit var runningRecordInteractor: RunningRecordInteractor 37 41 38 42 @Inject ··· 43 47 DomainAPI( 44 48 prefsInteractor, 45 49 recordTypeInteractor, 50 + recordTagInteractor, 46 51 runningRecordInteractor, 47 52 appColorMapper, 48 53 ),
+1 -1
app/src/main/res/values/wear.xml
··· 9 9 <item>/stt//GET/activities</item> 10 10 <item>/stt//GET/activities/current</item> 11 11 <item>/stt//POST/activities/current</item> 12 - <item>/stt//GET/activites/:ID/tags</item> 12 + <item>/stt//GET/activities/:ID/tags</item> 13 13 <item>/stt//GET/settings</item> 14 14 </string-array> 15 15 </resources>
+2 -8
wear/src/main/java/com/example/util/simpletimetracker/presentation/ActivityChip.kt
··· 27 27 import java.time.format.DateTimeFormatter 28 28 29 29 @Composable 30 - fun ActivityChip(activity: Activity, startedAt: Long? = null) { 30 + fun ActivityChip(activity: Activity, startedAt: Long? = null, onClick: () -> Unit = {}) { 31 31 val context = LocalContext.current 32 32 val briefIcon = if (activity.icon.startsWith("ic_")) { 33 33 "?" ··· 64 64 colors = ChipDefaults.chipColors( 65 65 backgroundColor = color, 66 66 ), 67 - onClick = { 68 - Toast.makeText( 69 - context, 70 - "Starting `${activity.name}` (id: ${activity.id})\n[Not Yet Implemented]", 71 - Toast.LENGTH_SHORT, 72 - ).show() 73 - }, 67 + onClick = onClick, 74 68 ) 75 69 } 76 70
+23 -4
wear/src/main/java/com/example/util/simpletimetracker/presentation/MainActivity.kt
··· 23 23 import androidx.compose.runtime.mutableIntStateOf 24 24 import androidx.compose.runtime.mutableStateOf 25 25 import androidx.compose.runtime.remember 26 + import androidx.compose.runtime.rememberCoroutineScope 26 27 import androidx.compose.runtime.setValue 27 28 import androidx.compose.ui.Modifier 28 29 import androidx.compose.ui.platform.LocalContext ··· 50 51 import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll 51 52 import kotlinx.coroutines.Dispatchers 52 53 import kotlinx.coroutines.async 54 + import kotlinx.coroutines.launch 53 55 54 56 class MainActivity : ComponentActivity() { 55 57 override fun onCreate(savedInstanceState: Bundle?) { ··· 85 87 @Composable 86 88 fun ActivityList(scrollState: ScalingLazyListState = rememberScalingLazyListState()) { 87 89 val context = LocalContext.current 90 + val composableScope = rememberCoroutineScope() 91 + val rpc = WearRPCClient(ContextMessenger(context)) 88 92 89 93 var activities: Array<Activity> by remember { mutableStateOf(arrayOf()) } 90 94 var activitiesQueryCount by remember { mutableIntStateOf(0) } ··· 92 96 key1 = activitiesQueryCount, 93 97 block = { 94 98 async(Dispatchers.Default) { 95 - activities = WearRPCClient(ContextMessenger(context)).queryActivities() 99 + activities = rpc.queryActivities() 96 100 } 97 101 }, 98 102 ) ··· 103 107 key1 = activitiesQueryCount, 104 108 block = { 105 109 async(Dispatchers.Default) { 106 - currentActivities = 107 - WearRPCClient(ContextMessenger(context)).queryCurrentActivities() 110 + currentActivities = rpc.queryCurrentActivities() 108 111 } 109 112 }, 110 113 ) ··· 125 128 val startedAt = 126 129 currentActivities.filter { it.id == activity.id }.getOrNull(0)?.startedAt 127 130 item { 128 - ActivityChip(activity, startedAt) 131 + ActivityChip( 132 + activity, startedAt, 133 + onClick = { 134 + composableScope.launch(Dispatchers.Default){ 135 + val tags = rpc.queryTagsForActivity(activity.id) 136 + composableScope.launch(Dispatchers.Main) { 137 + Toast.makeText( 138 + context, 139 + "`${activity.name}` (id: ${activity.id}) has tags [${ 140 + tags.joinToString(",") { it.name } 141 + }]", 142 + Toast.LENGTH_SHORT, 143 + ).show() 144 + } 145 + } 146 + }, 147 + ) 129 148 } 130 149 } 131 150 item {