refactor(timeago): consolidate toggle into one stable composable
Audit follow-up — the toggle behaviour now lives in a single `ToggleableTimeAgoText` core in `ui/note/elements/TimeAgo.kt`. `TimeAgo`, `NormalTimeAgo`, `ChatTimeAgo`, and `ChatroomHeaderCompose.TimeAgo` are thin wrappers that pick a `TimeAgoStyle` (Dotted / Short) and pass colour/font params; no per-site duplication of state + clickable + derivedStateOf. Performance fixes that matter for a feed with hundreds of timestamps: - `rememberSaveable` → `remember`. Persisting a transient peek-toggle to the SavedStateRegistry for every visible+scrolled-past note was pure memory bloat. Recycling now resets to relative, which is the expected behaviour for a transient inspect action. - The relative-mode `derivedStateOf` lambda reads `nowState.value` only when displaying a relative time. An item the user has frozen to its absolute date no longer re-evaluates every 30-second tick. - Core composable takes only stable primitive params (Long, Color, TextUnit, TextOverflow, enum) so Compose can skip it entirely when inputs don't change. - Desktop wrapper dropped the redundant `derivedStateOf`: its formatter reads no State, so derivedStateOf had nothing to observe. https://claude.ai/code/session_01AuPon9VQeRfKV1BTVQuKGC
This commit is contained in:
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.note.elements
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -29,11 +30,12 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAbsolute
|
import com.vitorpamplona.amethyst.ui.note.timeAbsolute
|
||||||
@@ -43,6 +45,85 @@ import com.vitorpamplona.amethyst.ui.note.timeAgoShort
|
|||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which relative-time format to use when the timestamp is *not* toggled to absolute.
|
||||||
|
* Absolute-mode uses [timeAbsolute] (dotted) or [timeAbsoluteNoDot] regardless.
|
||||||
|
*/
|
||||||
|
enum class TimeAgoStyle {
|
||||||
|
/** "• 5m", uses [timeAgo]. Used by note timestamps and chatroom row last-message time. */
|
||||||
|
Dotted,
|
||||||
|
|
||||||
|
/** "5m" (no dot, no leading space), uses [timeAgoShort]. Used by chat bubbles and channel headers. */
|
||||||
|
Short,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core toggleable timestamp Text. All on-screen TimeAgo composables delegate to this.
|
||||||
|
*
|
||||||
|
* Behaviour:
|
||||||
|
* - Renders the timestamp relative to "now" (subscribing to [LocalNowSeconds]); on click,
|
||||||
|
* flips to an absolute date/time formatted by [timeAbsolute] / [timeAbsoluteNoDot].
|
||||||
|
* - When displaying the absolute form we **stop subscribing to the tick** — the
|
||||||
|
* `derivedStateOf` lambda doesn't read `nowState.value` in that branch, so the Text
|
||||||
|
* does not recompose every 30s for items the user has "frozen" to a specific time.
|
||||||
|
* - Uses [remember] (not `rememberSaveable`): the toggle is a transient peek, not state
|
||||||
|
* worth saving to the SavedStateRegistry for every visible+scrolled-past note in the
|
||||||
|
* feed. Recycling resets to relative, which matches how peeking should work.
|
||||||
|
* - A single [MutableInteractionSource] per item, no ripple — tiny timestamp Text
|
||||||
|
* doesn't benefit from the indication.
|
||||||
|
*
|
||||||
|
* Stable params: all values, so the Compose compiler can skip this entirely when nothing changes.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ToggleableTimeAgoText(
|
||||||
|
timestamp: Long,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
style: TimeAgoStyle = TimeAgoStyle.Dotted,
|
||||||
|
color: Color = LocalContentColor.current,
|
||||||
|
fontSize: TextUnit = TextUnit.Unspecified,
|
||||||
|
maxLines: Int = 1,
|
||||||
|
overflow: TextOverflow = TextOverflow.Clip,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val nowState = LocalNowSeconds.current
|
||||||
|
val nowStr = stringRes(id = R.string.now)
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
var showAbsolute by remember(timestamp) { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val text by
|
||||||
|
remember(timestamp, context, style, nowState, nowStr) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (showAbsolute) {
|
||||||
|
when (style) {
|
||||||
|
TimeAgoStyle.Dotted -> timeAbsolute(timestamp, context)
|
||||||
|
TimeAgoStyle.Short -> timeAbsoluteNoDot(timestamp, context)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Read nowState only when displaying a relative time, so an item
|
||||||
|
// toggled to absolute doesn't recompose on every 30-second tick.
|
||||||
|
nowState.value
|
||||||
|
when (style) {
|
||||||
|
TimeAgoStyle.Dotted -> timeAgo(timestamp, context)
|
||||||
|
TimeAgoStyle.Short -> timeAgoShort(timestamp, nowStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = color,
|
||||||
|
fontSize = fontSize,
|
||||||
|
maxLines = maxLines,
|
||||||
|
overflow = overflow,
|
||||||
|
modifier =
|
||||||
|
modifier.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
) { showAbsolute = !showAbsolute },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimeAgo(note: Note) {
|
fun TimeAgo(note: Note) {
|
||||||
val time = note.createdAt() ?: return
|
val time = note.createdAt() ?: return
|
||||||
@@ -51,30 +132,10 @@ fun TimeAgo(note: Note) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimeAgo(time: Long) {
|
fun TimeAgo(time: Long) {
|
||||||
val context = LocalContext.current
|
ToggleableTimeAgoText(
|
||||||
// Subscribe to the shared coarse ticker; `derivedStateOf` ensures the Text only
|
timestamp = time,
|
||||||
// recomposes when the formatted string actually flips (e.g. 1m → 2m), not on every tick.
|
style = TimeAgoStyle.Dotted,
|
||||||
val nowState = LocalNowSeconds.current
|
|
||||||
var showAbsolute by rememberSaveable(time) { mutableStateOf(false) }
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
|
|
||||||
val timeStr by
|
|
||||||
remember(time, context, nowState) {
|
|
||||||
derivedStateOf {
|
|
||||||
nowState.value
|
|
||||||
if (showAbsolute) timeAbsolute(time, context) else timeAgo(time, context = context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = timeStr,
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
maxLines = 1,
|
|
||||||
modifier =
|
|
||||||
Modifier.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null,
|
|
||||||
) { showAbsolute = !showAbsolute },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,29 +144,11 @@ fun NormalTimeAgo(
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
) {
|
) {
|
||||||
val nowStr = stringRes(id = R.string.now)
|
val time = baseNote.createdAt() ?: 0L
|
||||||
val nowState = LocalNowSeconds.current
|
ToggleableTimeAgoText(
|
||||||
val context = LocalContext.current
|
timestamp = time,
|
||||||
var showAbsolute by rememberSaveable(baseNote.idHex) { mutableStateOf(false) }
|
style = TimeAgoStyle.Short,
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
|
|
||||||
val time by
|
|
||||||
remember(baseNote, nowStr, nowState) {
|
|
||||||
derivedStateOf {
|
|
||||||
nowState.value
|
|
||||||
val createdAt = baseNote.createdAt() ?: 0L
|
|
||||||
if (showAbsolute) timeAbsoluteNoDot(createdAt, context) else timeAgoShort(createdAt, nowStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = time,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier =
|
modifier = modifier,
|
||||||
modifier.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null,
|
|
||||||
) { showAbsolute = !showAbsolute },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-33
@@ -20,20 +20,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -42,9 +35,8 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.LocalNowSeconds
|
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgoStyle
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAbsoluteNoDot
|
import com.vitorpamplona.amethyst.ui.note.elements.ToggleableTimeAgoText
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgoShort
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAheadNoDot
|
import com.vitorpamplona.amethyst.ui.note.timeAheadNoDot
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||||
@@ -54,31 +46,11 @@ import com.vitorpamplona.quartz.nip40Expiration.expiration
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatTimeAgo(baseNote: Note) {
|
fun ChatTimeAgo(baseNote: Note) {
|
||||||
val nowStr = stringRes(id = R.string.now)
|
ToggleableTimeAgoText(
|
||||||
val nowState = LocalNowSeconds.current
|
timestamp = baseNote.createdAt() ?: 0L,
|
||||||
val context = LocalContext.current
|
style = TimeAgoStyle.Short,
|
||||||
var showAbsolute by rememberSaveable(baseNote.idHex) { mutableStateOf(false) }
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
|
|
||||||
val time by
|
|
||||||
remember(baseNote, nowStr, nowState) {
|
|
||||||
derivedStateOf {
|
|
||||||
nowState.value
|
|
||||||
val createdAt = baseNote.createdAt() ?: 0L
|
|
||||||
if (showAbsolute) timeAbsoluteNoDot(createdAt, context) else timeAgoShort(createdAt, nowStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = time,
|
|
||||||
color = MaterialTheme.colorScheme.placeholderText,
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
fontSize = Font12SP,
|
fontSize = Font12SP,
|
||||||
maxLines = 1,
|
|
||||||
modifier =
|
|
||||||
Modifier.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null,
|
|
||||||
) { showAbsolute = !showAbsolute },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-28
@@ -20,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
@@ -29,14 +27,11 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -64,9 +59,8 @@ import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContentOrNull
|
|||||||
import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel
|
import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.LocalNowSeconds
|
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgoStyle
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAbsolute
|
import com.vitorpamplona.amethyst.ui.note.elements.ToggleableTimeAgoText
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.RoomNameDisplay
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.RoomNameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel
|
||||||
@@ -487,27 +481,10 @@ fun ChannelName(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TimeAgo(channelLastTime: Long?) {
|
private fun TimeAgo(channelLastTime: Long?) {
|
||||||
if (channelLastTime == null) return
|
if (channelLastTime == null) return
|
||||||
|
ToggleableTimeAgoText(
|
||||||
val context = LocalContext.current
|
timestamp = channelLastTime,
|
||||||
val nowState = LocalNowSeconds.current
|
style = TimeAgoStyle.Dotted,
|
||||||
var showAbsolute by rememberSaveable(channelLastTime) { mutableStateOf(false) }
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val timeAgo by
|
|
||||||
remember(channelLastTime, context, nowState) {
|
|
||||||
derivedStateOf {
|
|
||||||
nowState.value
|
|
||||||
if (showAbsolute) timeAbsolute(channelLastTime, context) else timeAgo(channelLastTime, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = timeAgo,
|
|
||||||
color = MaterialTheme.colorScheme.grayText,
|
color = MaterialTheme.colorScheme.grayText,
|
||||||
maxLines = 1,
|
|
||||||
modifier =
|
|
||||||
Modifier.clickable(
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = null,
|
|
||||||
) { showAbsolute = !showAbsolute },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -27,7 +27,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -39,8 +38,16 @@ import com.vitorpamplona.amethyst.commons.util.toTimeAgo
|
|||||||
* Timestamp text that toggles between a relative ("5m") and an absolute, scale-adjusted
|
* Timestamp text that toggles between a relative ("5m") and an absolute, scale-adjusted
|
||||||
* ("14:32" / "Dec 12, 14:32" / "Dec 12, 2023") form when the user clicks it.
|
* ("14:32" / "Dec 12, 14:32" / "Dec 12, 2023") form when the user clicks it.
|
||||||
*
|
*
|
||||||
* The toggle is local to the call site and persists across recomposition via
|
* Notes for the audit:
|
||||||
* [rememberSaveable], so it survives scroll-induced disposal in a LazyColumn.
|
* - Plain [remember] (not `rememberSaveable`): the toggle is a transient peek. Persisting
|
||||||
|
* it for every visible+scrolled-past notification/chat/note across config changes is
|
||||||
|
* pure memory bloat — recycling resets to relative, which is the right default.
|
||||||
|
* - Both formatters here are pure functions of the timestamp, so for desktop we don't need
|
||||||
|
* any tick subscription at all (desktop has no shared "now" ticker). The string is
|
||||||
|
* stable for a given (timestamp, showAbsolute) pair, so a [derivedStateOf] also isn't
|
||||||
|
* needed — we just compute on flip.
|
||||||
|
* - All params are primitives/value types, so the Compose compiler can skip this composable
|
||||||
|
* when nothing changes.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ToggleableTimeAgoText(
|
fun ToggleableTimeAgoText(
|
||||||
@@ -49,13 +56,11 @@ fun ToggleableTimeAgoText(
|
|||||||
color: Color,
|
color: Color,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var showAbsolute by rememberSaveable(timestamp) { mutableStateOf(false) }
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val text =
|
var showAbsolute by remember(timestamp) { mutableStateOf(false) }
|
||||||
if (showAbsolute) timeAbsolute(timestamp, withDot = false) else timestamp.toTimeAgo(withDot = false)
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = if (showAbsolute) timeAbsolute(timestamp, withDot = false) else timestamp.toTimeAgo(withDot = false),
|
||||||
style = style,
|
style = style,
|
||||||
color = color,
|
color = color,
|
||||||
modifier =
|
modifier =
|
||||||
|
|||||||
Reference in New Issue
Block a user