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.

refactor: Move active tags to secondary label

More text for the tags fits on the secondary label, especially after
@kantahrek's rewrite of "Since" to a live duration.

Joseph Hale (Feb 28, 2024, 9:12 PM -0700) 197be5a6 d9109c9a

+46 -37
+21 -37
wear/src/main/java/com/example/util/simpletimetracker/presentation/components/ActivityChip.kt
··· 11 11 import androidx.compose.foundation.layout.height 12 12 import androidx.compose.foundation.layout.padding 13 13 import androidx.compose.runtime.Composable 14 - import androidx.compose.runtime.LaunchedEffect 15 - import androidx.compose.runtime.getValue 16 - import androidx.compose.runtime.mutableStateOf 17 - import androidx.compose.runtime.remember 18 - import androidx.compose.runtime.setValue 19 14 import androidx.compose.ui.Modifier 20 15 import androidx.compose.ui.graphics.Color 21 16 import androidx.compose.ui.semantics.contentDescription ··· 30 25 import androidx.wear.compose.material.Text 31 26 import androidx.wear.compose.material.ToggleChipDefaults 32 27 import androidx.wear.tooling.preview.devices.WearDevices 28 + import com.example.util.simpletimetracker.presentation.remember.rememberDurationSince 33 29 import com.example.util.simpletimetracker.wearrpc.Activity 34 30 import com.example.util.simpletimetracker.wearrpc.Tag 35 - import kotlinx.coroutines.time.delay 36 31 import java.time.Duration 37 32 import java.time.Instant 38 - import java.time.LocalDateTime 39 - import java.time.ZoneId 40 - import java.time.format.DateTimeFormatter 41 33 42 34 private const val ISO_HOURS_MINUTES_PARTS_REGEX = "(\\d[HM])(?!$)" 43 35 private const val DECIMAL_SEPARATOR_AND_FRACTIONAL_PART_REGEX = "\\.\\d+" ··· 58 50 "" 59 51 } 60 52 val tagString = if (tagsList.isNotEmpty()) { 61 - " ($tagsList)" 53 + " - $tagsList" 62 54 } else { 63 55 "" 64 56 } ··· 71 63 Row(modifier = Modifier.height(IntrinsicSize.Min)) { 72 64 ActivityIcon(activityIcon = activity.icon) 73 65 Text( 74 - text = activity.name + tagString, 66 + text = activity.name, 75 67 maxLines = 1, 76 68 overflow = TextOverflow.Ellipsis, 77 69 modifier = Modifier.padding(start = 4.dp), ··· 80 72 }, 81 73 secondaryLabel = { 82 74 if (startedAt != null) { 83 - var startedDiff by remember { mutableStateOf(Duration.between(Instant.ofEpochMilli(startedAt), Instant.now())) } 75 + var startedDiff = rememberDurationSince(epochMillis = startedAt) 84 76 85 77 Text( 86 - text = durationToLabel(startedDiff), 78 + text = durationToLabel(startedDiff) + tagString, 79 + maxLines = 1, 80 + overflow = TextOverflow.Ellipsis, 87 81 color = Color.White, 88 82 fontSize = 10.sp, 89 - modifier = Modifier.padding(start = 22.dp), 83 + modifier = Modifier.padding( 84 + start = if (tagString.isNotEmpty()) { 85 + 2.dp 86 + } else { 87 + 22.dp 88 + }, 89 + ), 90 90 ) 91 - 92 - LaunchedEffect(startedDiff) { 93 - delay(Duration.ofSeconds(1L)) 94 - startedDiff = Duration.between(Instant.ofEpochMilli(startedAt), Instant.now()) 95 - } 96 91 } else { 97 92 null 98 93 } ··· 137 132 .substring(2) // remove "PT" at the beginning of the string representation 138 133 .replace(ISO_HOURS_MINUTES_PARTS_REGEX.toRegex(), "$1 ") 139 134 .replace(DECIMAL_SEPARATOR_AND_FRACTIONAL_PART_REGEX.toRegex(), "") 140 - .replace(ISO_MISSING_MINUTES_REGEX.toRegex(), "$1 0M $2") 141 - .lowercase() 142 - } 143 - 144 - fun recentTimestampToString(epochMillis: Long): String { 145 - // Someday, it would be nice for this to show nicer time strings 146 - // e.g. "a few minutes ago", "yesterday", etc. 147 - val time = LocalDateTime.ofInstant( 148 - Instant.ofEpochMilli(epochMillis), 149 - ZoneId.systemDefault(), 150 - ) 151 - return if (time > LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).minusDays(1)) { 152 - time.format(DateTimeFormatter.ISO_LOCAL_TIME) 153 - } else { 154 - time.format(DateTimeFormatter.ISO_DATE_TIME).replace("T", " ") 155 - } 135 + .replace(ISO_MISSING_MINUTES_REGEX.toRegex(), "$1 0M $2").lowercase() 156 136 } 157 137 158 138 @Preview(device = WearDevices.LARGE_ROUND) ··· 197 177 @Preview(device = WearDevices.LARGE_ROUND) 198 178 @Composable 199 179 fun CurrentlyRunning() { 200 - ActivityChip(Activity(456, "Sleeping", "🛏️", 0xFFABCDEF), startedAt = 1706751601000L) 180 + ActivityChip( 181 + Activity(456, "Sleeping", "🛏️", 0xFFABCDEF), 182 + startedAt = Instant.now().toEpochMilli() - 360000, 183 + ) 201 184 } 202 185 203 186 @Preview(device = WearDevices.LARGE_ROUND) 204 187 @Composable 205 188 fun CurrentlyRunningWithTags() { 206 189 ActivityChip( 207 - Activity(456, "Sleeping", "🛏️", 0xFFABCDEF), startedAt = 1706751601000L, 190 + Activity(456, "Sleeping", "🛏️", 0xFFABCDEF), 191 + startedAt = Instant.now().toEpochMilli() - 360000, 208 192 tags = arrayOf( 209 193 Tag(id = 2, name = "Work", isGeneral = true, color = 0xFFFFAA22), 210 194 Tag(id = 4, name = "Hotel", isGeneral = false, color = 0xFFABCDEF),
+25
wear/src/main/java/com/example/util/simpletimetracker/presentation/remember/durationSince.kt
··· 1 + package com.example.util.simpletimetracker.presentation.remember 2 + 3 + import androidx.compose.runtime.Composable 4 + import androidx.compose.runtime.LaunchedEffect 5 + import androidx.compose.runtime.getValue 6 + import androidx.compose.runtime.mutableStateOf 7 + import androidx.compose.runtime.remember 8 + import androidx.compose.runtime.setValue 9 + import kotlinx.coroutines.time.delay 10 + import java.time.Duration 11 + import java.time.Instant 12 + 13 + @Composable 14 + fun rememberDurationSince(epochMillis: Long): Duration { 15 + var duration by remember { mutableStateOf(durationSince(epochMillis)) } 16 + LaunchedEffect(duration) { 17 + delay(Duration.ofSeconds(1L)) 18 + duration = durationSince(epochMillis) 19 + } 20 + return duration 21 + } 22 + 23 + private fun durationSince(epochMillis: Long): Duration { 24 + return Duration.between(Instant.ofEpochMilli(epochMillis), Instant.now()) 25 + }