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.

switch array to list in wear communication

razeeman (Mar 1, 2024, 11:29 PM +0300) ec9d01fe c9a4f807

+108 -120
+33 -36
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearCommunicationInteractor.kt
··· 5 5 */ 6 6 package com.example.util.simpletimetracker.feature_wear 7 7 8 + import com.example.util.simpletimetracker.domain.extension.orZero 8 9 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor 9 10 import com.example.util.simpletimetracker.domain.interactor.RecordTagInteractor 10 11 import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor ··· 30 31 private val appColorMapper: AppColorMapper, 31 32 ) : WearCommunicationAPI { 32 33 33 - override suspend fun queryActivities(): Array<Activity> { 34 + override suspend fun queryActivities(): List<Activity> { 34 35 return recordTypeInteractor.getAll() 35 36 .filter { recordType -> !recordType.hidden } 36 37 .map { recordType -> ··· 38 39 id = recordType.id, 39 40 name = recordType.name, 40 41 icon = recordType.icon, 41 - color = asColor(recordType.color), 42 + color = mapColor(recordType.color), 42 43 ) 43 - }.toTypedArray() 44 + } 44 45 } 45 46 46 - override suspend fun queryCurrentActivities(): Array<CurrentActivity> { 47 + override suspend fun queryCurrentActivities(): List<CurrentActivity> { 47 48 return runningRecordInteractor.getAll().map { record -> 48 49 CurrentActivity( 49 - record.id, 50 - record.timeStarted, 51 - record.tagIds.map { tagId -> 52 - asTag(recordTagInteractor.get(tagId)) 53 - }.filter { it.id > 0 }.toTypedArray(), 50 + id = record.id, 51 + startedAt = record.timeStarted, 52 + tags = record.tagIds.mapNotNull { tagId -> 53 + recordTagInteractor.get(tagId)?.let(::mapTag) 54 + }, 54 55 ) 55 - }.toTypedArray() 56 + } 56 57 } 57 58 58 - override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) { 59 + override suspend fun setCurrentActivities(activities: List<CurrentActivity>) { 59 60 val currents = queryCurrentActivities() 60 61 val unchanged = currents.filter { c -> activities.any { a -> a == c } } 61 62 val stopped = currents.filter { c -> unchanged.none { u -> u == c } } ··· 73 74 ) 74 75 } 75 76 76 - override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 77 - val activityColor = recordTypeInteractor.get(activityId)?.color 77 + override suspend fun queryTagsForActivity(activityId: Long): List<Tag> { 78 + val activity = recordTypeInteractor.get(activityId) ?: return emptyList() 79 + val activityColor = mapColor(activity.color) 78 80 return recordTagInteractor.getByTypeOrUntyped(activityId) 79 81 .filter { !it.archived } 80 - .map { asTag(it, asColor(activityColor)) } 82 + .map { mapTag(it, activityColor) } 81 83 .sortedBy { it.name } 82 84 .sortedBy { it.isGeneral } 83 - .toTypedArray() 84 85 } 85 86 86 - private fun asTag(recordTag: RecordTag?, activityColor: Long = 0x00000000): Tag { 87 - return if (recordTag != null) { 88 - val isGeneral = recordTag.typeId == 0L 89 - val tagColor = if (isGeneral) { 90 - asColor(recordTag.color) 91 - } else { 92 - activityColor 93 - } 94 - Tag( 95 - id = recordTag.id, 96 - name = recordTag.name, 97 - isGeneral = isGeneral, 98 - color = tagColor, 99 - ) 87 + private fun mapTag( 88 + recordTag: RecordTag, 89 + activityColor: Long? = null, 90 + ): Tag { 91 + val isGeneral = recordTag.typeId == 0L 92 + val tagColor = if (isGeneral) { 93 + mapColor(recordTag.color) 100 94 } else { 101 - Tag(id = -1, name = "", isGeneral = true, color = 0xFF555555) 95 + activityColor 102 96 } 97 + 98 + return Tag( 99 + id = recordTag.id, 100 + name = recordTag.name, 101 + isGeneral = isGeneral, 102 + color = tagColor.orZero(), 103 + ) 103 104 } 104 105 105 - private fun asColor(appColor: AppColor?): Long { 106 - return if (appColor == null) { 107 - 0x00000000 108 - } else { 109 - appColorMapper.mapToColorInt(appColor).toLong() 110 - } 106 + private fun mapColor(appColor: AppColor): Long { 107 + return appColorMapper.mapToColorInt(appColor).toLong() 111 108 } 112 109 113 110 override suspend fun querySettings(): Settings {
+1 -1
features/feature_wear/src/main/java/com/example/util/simpletimetracker/feature_wear/WearRPCServer.kt
··· 44 44 } 45 45 46 46 private suspend fun onSetCurrentActivities(request: ByteArray): ByteArray? { 47 - val activities: Array<CurrentActivity> = mapRequest(request) ?: return null 47 + val activities: List<CurrentActivity> = mapRequest(request) ?: return null 48 48 api.setCurrentActivities(activities) 49 49 return ByteArray(0) 50 50 }
+8 -8
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/ActivitiesList.kt
··· 20 20 21 21 @Composable 22 22 fun ActivitiesList( 23 - activities: Array<Activity>, 24 - currentActivities: Array<CurrentActivity>, 23 + activities: List<Activity>, 24 + currentActivities: List<CurrentActivity>, 25 25 onSelectActivity: (activity: Activity) -> Unit, 26 26 onEnableActivity: (activity: Activity) -> Unit, 27 27 onDisableActivity: (activity: Activity) -> Unit, ··· 43 43 ActivityChip( 44 44 activity, 45 45 startedAt = currentActivity?.startedAt, 46 - tags = currentActivity?.tags ?: arrayOf(), 46 + tags = currentActivity?.tags.orEmpty(), 47 47 onClick = { onSelectActivity(activity) }, 48 48 onToggleOn = { onEnableActivity(activity) }, 49 49 onToggleOff = { onDisableActivity(activity) }, ··· 61 61 @Composable 62 62 private fun NoActivities() { 63 63 ActivitiesList( 64 - activities = arrayOf(), 65 - currentActivities = arrayOf(), 64 + activities = emptyList(), 65 + currentActivities = emptyList(), 66 66 onSelectActivity = { /* `it` is the selected activity */ }, 67 67 onEnableActivity = { /* `it` is the enabled activity */ }, 68 68 onDisableActivity = { /* `it` is the disabled activity */ }, ··· 73 73 @Preview(device = WearDevices.LARGE_ROUND) 74 74 @Composable 75 75 private fun Preview() { 76 - val activities = arrayOf( 76 + val activities = listOf( 77 77 Activity(1234, "Chores", "🧹", 0xFFFA0000), 78 78 Activity(4321, "Sleep", "🛏️", 0xFF0000FA), 79 79 ) 80 - val currents = arrayOf( 81 - CurrentActivity(id = 4321, startedAt = 1708241427000L, tags = arrayOf()), 80 + val currents = listOf( 81 + CurrentActivity(id = 4321, startedAt = 1708241427000L, tags = emptyList()), 82 82 ) 83 83 ActivitiesList( 84 84 activities = activities,
+2 -2
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/ActivityChip.kt
··· 39 39 fun ActivityChip( 40 40 activity: Activity, 41 41 startedAt: Long? = null, 42 - tags: Array<Tag> = arrayOf(), 42 + tags: List<Tag> = emptyList(), 43 43 onClick: () -> Unit = {}, 44 44 onToggleOn: () -> Unit = {}, 45 45 onToggleOff: () -> Unit = {}, ··· 189 189 ActivityChip( 190 190 Activity(456, "Sleeping", "🛏️", 0xFFABCDEF), 191 191 startedAt = Instant.now().toEpochMilli() - 360000, 192 - tags = arrayOf( 192 + tags = listOf( 193 193 Tag(id = 2, name = "Work", isGeneral = true, color = 0xFFFFAA22), 194 194 Tag(id = 4, name = "Hotel", isGeneral = false, color = 0xFFABCDEF), 195 195 ),
+8 -8
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/TagList.kt
··· 23 23 24 24 @Composable 25 25 fun TagList( 26 - tags: Array<Tag>, 26 + tags: List<Tag>, 27 27 mode: TagSelectionMode = TagSelectionMode.SINGLE, 28 - onSelectionComplete: (tags: Array<Tag>) -> Unit = {}, 28 + onSelectionComplete: (tags: List<Tag>) -> Unit = {}, 29 29 ) { 30 30 var selectedTags: List<Tag> by remember { mutableStateOf(listOf()) } 31 31 ScaffoldedScrollingColumn { ··· 42 42 TagChip( 43 43 tag = tag, mode = mode, 44 44 onClick = { 45 - onSelectionComplete(selectedTags.minus(tag).plus(tag).toTypedArray()) 46 - // ^No duplicate tags^ 45 + // No duplicate tags 46 + onSelectionComplete(selectedTags.minus(tag).plus(tag)) 47 47 }, 48 48 onToggleOn = { selectedTags = selectedTags.plus(tag) }, 49 49 onToggleOff = { selectedTags = selectedTags.minus(tag) }, ··· 51 51 } 52 52 } 53 53 } 54 - item { SubmitButton(onClick = { onSelectionComplete(selectedTags.toTypedArray()) }) } 54 + item { SubmitButton(onClick = { onSelectionComplete(selectedTags) }) } 55 55 } 56 56 } 57 57 58 58 @Preview(device = WearDevices.LARGE_ROUND) 59 59 @Composable 60 60 private fun NoTags() { 61 - TagList(tags = arrayOf(), onSelectionComplete = {}) 61 + TagList(tags = emptyList(), onSelectionComplete = {}) 62 62 } 63 63 64 64 @Preview(device = WearDevices.LARGE_ROUND) 65 65 @Composable 66 66 private fun WithSomeTags() { 67 67 TagList( 68 - tags = arrayOf( 68 + tags = listOf( 69 69 Tag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 70 70 Tag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 71 71 ), ··· 76 76 @Composable 77 77 private fun MultiSelectMode() { 78 78 TagList( 79 - tags = arrayOf( 79 + tags = listOf( 80 80 Tag(id = 123, name = "Sleep", isGeneral = false, color = 0xFF123456), 81 81 Tag(id = 124, name = "Personal", isGeneral = true, color = 0xFF123456), 82 82 ),
+5 -5
wear/src/main/java/com/example/util/simpletimetracker/presentation/mediators/CurrentActivitiesMediator.kt
··· 7 7 8 8 class CurrentActivitiesMediator( 9 9 private val rpc: WearRPCClient, 10 - private val currents: Array<CurrentActivity>, 10 + private val currents: List<CurrentActivity>, 11 11 ) { 12 12 suspend fun start(activityId: Long) { 13 - start(activityId, arrayOf()) 13 + start(activityId, emptyList()) 14 14 } 15 - suspend fun start(activityId: Long, tags: Array<Tag>) { 15 + suspend fun start(activityId: Long, tags: List<Tag>) { 16 16 val newCurrent = CurrentActivity( 17 17 id = activityId, 18 18 startedAt = System.currentTimeMillis(), ··· 21 21 if (settings().allowMultitasking) { 22 22 this.rpc.setCurrentActivities(currents.plus(newCurrent)) 23 23 } else { 24 - this.rpc.setCurrentActivities(arrayOf(newCurrent)) 24 + this.rpc.setCurrentActivities(listOf(newCurrent)) 25 25 } 26 26 } 27 27 28 28 suspend fun stop(currentId: Long) { 29 - val remaining = currents.filter { it.id != currentId }.toTypedArray() 29 + val remaining = currents.filter { it.id != currentId } 30 30 this.rpc.setCurrentActivities(remaining) 31 31 } 32 32 private suspend fun settings(): Settings {
+3 -4
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/activities.kt
··· 13 13 import androidx.compose.runtime.remember 14 14 import androidx.compose.runtime.setValue 15 15 import com.example.util.simpletimetracker.wearrpc.Activity 16 - import com.example.util.simpletimetracker.wearrpc.WearRPCClient 17 16 import kotlinx.coroutines.Dispatchers 18 17 import kotlinx.coroutines.async 19 18 ··· 34 33 * from the phone. 35 34 */ 36 35 @Composable 37 - fun rememberActivities(): Pair<Array<Activity>, () -> Unit> { 38 - var rpc = rememberRPCClient() 39 - var activities: Array<Activity> by remember { mutableStateOf(arrayOf()) } 36 + fun rememberActivities(): Pair<List<Activity>, () -> Unit> { 37 + val rpc = rememberRPCClient() 38 + var activities: List<Activity> by remember { mutableStateOf(emptyList()) } 40 39 var activitiesQueryCount by remember { mutableIntStateOf(0) } 41 40 LaunchedEffect(activitiesQueryCount) { 42 41 async(Dispatchers.Default) {
+4 -4
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/currentActivities.kt
··· 35 35 * from the phone. 36 36 */ 37 37 @Composable 38 - fun rememberCurrentActivities(): Pair<Array<CurrentActivity>, () -> Unit> { 39 - var rpc = rememberRPCClient() 40 - var currentActivities: Array<CurrentActivity> by remember { mutableStateOf(arrayOf()) } 38 + fun rememberCurrentActivities(): Pair<List<CurrentActivity>, () -> Unit> { 39 + val rpc = rememberRPCClient() 40 + var currentActivities: List<CurrentActivity> by remember { mutableStateOf(emptyList()) } 41 41 var currentActivitiesQueryCount by remember { mutableIntStateOf(0) } 42 42 val queryCurrentActivities = { currentActivitiesQueryCount++ } 43 43 LaunchedEffect(currentActivitiesQueryCount) { ··· 45 45 currentActivities = rpc.queryCurrentActivities() 46 46 } 47 47 } 48 - return Pair(currentActivities, { queryCurrentActivities() }) 48 + return currentActivities to { queryCurrentActivities() } 49 49 }
+3 -4
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/tags.kt
··· 13 13 import androidx.compose.runtime.remember 14 14 import androidx.compose.runtime.setValue 15 15 import com.example.util.simpletimetracker.wearrpc.Tag 16 - import com.example.util.simpletimetracker.wearrpc.WearRPCClient 17 16 import kotlinx.coroutines.Dispatchers 18 17 import kotlinx.coroutines.async 19 18 ··· 35 34 * from the phone. 36 35 */ 37 36 @Composable 38 - fun rememberTags(activityId: Long): Pair<Array<Tag>, () -> Unit> { 39 - var rpc = rememberRPCClient() 40 - var tags: Array<Tag> by remember { mutableStateOf(arrayOf()) } 37 + fun rememberTags(activityId: Long): Pair<List<Tag>, () -> Unit> { 38 + val rpc = rememberRPCClient() 39 + var tags: List<Tag> by remember { mutableStateOf(emptyList()) } 41 40 var tagsQueryCount by remember { mutableIntStateOf(0) } 42 41 LaunchedEffect(tagsQueryCount) { 43 42 async(Dispatchers.Default) {
+17 -23
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/DTO.kt
··· 11 11 * Object definitions for records sent between Wear/Mobile 12 12 */ 13 13 14 - data class Activity(val id: Long, val name: String, val icon: String, val color: Long) 15 - 16 - data class CurrentActivity(val id: Long, val startedAt: Long, val tags: Array<Tag>) { 17 - override fun equals(other: Any?): Boolean { 18 - // autogenerated 19 - if (this === other) return true 20 - if (javaClass != other?.javaClass) return false 21 - 22 - other as CurrentActivity 23 - 24 - if (id != other.id) return false 25 - if (startedAt != other.startedAt) return false 26 - return tags.contentEquals(other.tags) 27 - } 14 + data class Activity( 15 + val id: Long, 16 + val name: String, 17 + val icon: String, 18 + val color: Long, 19 + ) 28 20 29 - override fun hashCode(): Int { 30 - // autogenerated 31 - var result = id.toInt() 32 - result = 31 * result + startedAt.hashCode() 33 - result = 31 * result + tags.contentHashCode() 34 - return result 35 - } 36 - } 21 + data class CurrentActivity( 22 + val id: Long, 23 + val startedAt: Long, 24 + val tags: List<Tag>, 25 + ) 37 26 38 - data class Tag(val id: Long, val name: String, val isGeneral: Boolean, val color: Long) 27 + data class Tag( 28 + val id: Long, 29 + val name: String, 30 + val isGeneral: Boolean, 31 + val color: Long, 32 + ) 39 33 40 34 data class Settings( 41 35 val allowMultitasking: Boolean,
+13 -13
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/MockWearCommunicationAPI.kt
··· 2 2 3 3 class MockWearCommunicationAPI : WearCommunicationAPI { 4 4 5 - var activities: Array<Activity> = arrayOf() 6 - var currentActivities: Array<CurrentActivity> = arrayOf() 7 - var tags: Map<Long, Array<Tag>> = mapOf() 5 + var activities: List<Activity> = emptyList() 6 + var currentActivities: List<CurrentActivity> = emptyList() 7 + var tags: Map<Long, List<Tag>> = mapOf() 8 8 lateinit var settings: Settings 9 9 10 - override suspend fun queryActivities(): Array<Activity> { 10 + override suspend fun queryActivities(): List<Activity> { 11 11 return activities 12 12 } 13 13 14 - fun mock_queryActivities(activities: Array<Activity>) { 14 + fun mock_queryActivities(activities: List<Activity>) { 15 15 this.activities = activities 16 16 } 17 17 18 - override suspend fun queryCurrentActivities(): Array<CurrentActivity> { 18 + override suspend fun queryCurrentActivities(): List<CurrentActivity> { 19 19 return currentActivities 20 20 } 21 21 22 - fun mock_queryCurrentActivities(activities: Array<CurrentActivity>) { 22 + fun mock_queryCurrentActivities(activities: List<CurrentActivity>) { 23 23 this.currentActivities = activities 24 24 } 25 25 26 - override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) { 26 + override suspend fun setCurrentActivities(activities: List<CurrentActivity>) { 27 27 TODO("Not yet implemented") 28 28 } 29 29 30 - override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 31 - return this.tags[activityId] ?: arrayOf() 30 + override suspend fun queryTagsForActivity(activityId: Long): List<Tag> { 31 + return this.tags[activityId].orEmpty() 32 32 } 33 33 34 - fun mock_queryTagsForActivity(tags: Map<Long, Array<Tag>>) { 34 + fun mock_queryTagsForActivity(tags: Map<Long, List<Tag>>) { 35 35 this.tags = tags 36 36 } 37 37 ··· 44 44 } 45 45 46 46 fun mockReset() { 47 - this.activities = arrayOf() 48 - this.currentActivities = arrayOf() 47 + this.activities = emptyList() 48 + this.currentActivities = emptyList() 49 49 this.tags = mapOf() 50 50 } 51 51
+4 -4
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearCommunicationAPI.kt
··· 22 22 * 23 23 * Retrieves a list of all the time-tracking activities available for selection 24 24 */ 25 - suspend fun queryActivities(): Array<Activity> 25 + suspend fun queryActivities(): List<Activity> 26 26 27 27 /** 28 28 * [Request.QUERY_CURRENT_ACTIVITIES] 29 29 * 30 30 * Retrieves a list of the currently running activity/activities 31 31 */ 32 - suspend fun queryCurrentActivities(): Array<CurrentActivity> 32 + suspend fun queryCurrentActivities(): List<CurrentActivity> 33 33 34 34 /** 35 35 * [Request.SET_CURRENT_ACTIVITIES] 36 36 * 37 37 * Replaces the currently running activity/activities with the given activities 38 38 */ 39 - suspend fun setCurrentActivities(activities: Array<CurrentActivity>) 39 + suspend fun setCurrentActivities(activities: List<CurrentActivity>) 40 40 41 41 /** 42 42 * [Request.QUERY_TAGS_FOR_ACTIVITY] 43 43 * 44 44 * Retrieves the tags available for association with the activity with the given ID 45 45 */ 46 - suspend fun queryTagsForActivity(activityId: Long): Array<Tag> 46 + suspend fun queryTagsForActivity(activityId: Long): List<Tag> 47 47 48 48 /** 49 49 * [Request.QUERY_SETTINGS]
+7 -8
wearrpc/src/main/java/com/example/util/simpletimetracker/wearrpc/WearRPCClient.kt
··· 8 8 import com.google.gson.Gson 9 9 import com.google.gson.reflect.TypeToken 10 10 11 - 12 11 class WearRPCClient(private val messenger: Messenger) : WearCommunicationAPI { 13 12 14 13 override suspend fun ping(message: String): String { ··· 17 16 else throw WearRPCException("No response") 18 17 } 19 18 20 - override suspend fun queryActivities(): Array<Activity> { 19 + override suspend fun queryActivities(): List<Activity> { 21 20 val response = messenger.send(Request.QUERY_ACTIVITIES) 22 21 if (response != null) { 23 - val collectionType = object : TypeToken<Array<Activity>>() {}.type 22 + val collectionType = object : TypeToken<List<Activity>>() {}.type 24 23 return Gson().fromJson(String(response), collectionType) 25 24 } else throw WearRPCException("No response") 26 25 } 27 26 28 - override suspend fun queryCurrentActivities(): Array<CurrentActivity> { 27 + override suspend fun queryCurrentActivities(): List<CurrentActivity> { 29 28 val response = messenger.send(Request.QUERY_CURRENT_ACTIVITIES) 30 29 if (response != null) { 31 - val collectionType = object : TypeToken<Array<CurrentActivity>>() {}.type 30 + val collectionType = object : TypeToken<List<CurrentActivity>>() {}.type 32 31 return Gson().fromJson(String(response), collectionType) 33 32 } else throw WearRPCException("No response") 34 33 } 35 34 36 - override suspend fun setCurrentActivities(activities: Array<CurrentActivity>) { 35 + override suspend fun setCurrentActivities(activities: List<CurrentActivity>) { 37 36 messenger.send(Request.SET_CURRENT_ACTIVITIES, Gson().toJson(activities).toByteArray()) 38 37 } 39 38 40 - override suspend fun queryTagsForActivity(activityId: Long): Array<Tag> { 39 + override suspend fun queryTagsForActivity(activityId: Long): List<Tag> { 41 40 val response = messenger.send( 42 41 Request.QUERY_TAGS_FOR_ACTIVITY, 43 42 Gson().toJson(activityId).toByteArray(), 44 43 ) 45 44 if (response != null) { 46 - val collectionType = object : TypeToken<Array<Tag>>() {}.type 45 + val collectionType = object : TypeToken<List<Tag>>() {}.type 47 46 return Gson().fromJson(String(response), collectionType) 48 47 } else throw WearRPCException("No response") 49 48 }