refactor(android): use shared formatDuration + ms/sec conversions

This commit is contained in:
2026-05-29 15:46:56 -04:00
parent cf0ccd1f90
commit 3e35127284
6 changed files with 15 additions and 46 deletions
@@ -11,6 +11,8 @@ import com.fabledsword.minstrel.models.wire.AlbumWire
import com.fabledsword.minstrel.models.wire.ArtistDetailWire
import com.fabledsword.minstrel.models.wire.ArtistWire
import com.fabledsword.minstrel.models.wire.TrackWire
import com.fabledsword.minstrel.shared.millisToSeconds
import com.fabledsword.minstrel.shared.secondsToMillis
// Wire to Entity — used after a successful sync/fetch to persist into Room.
// Entity fields are a subset of wire fields (display names + sort keys);
@@ -37,7 +39,7 @@ fun TrackWire.toEntity(): CachedTrackEntity =
albumId = albumId,
artistId = artistId,
title = title,
durationMs = durationSec * MILLIS_PER_SECOND,
durationMs = durationSec.secondsToMillis(),
trackNumber = trackNumber,
discNumber = discNumber,
)
@@ -94,7 +96,7 @@ fun CachedTrackEntity.toDomain(
artistName = artistName,
trackNumber = trackNumber,
discNumber = discNumber,
durationSec = durationMs / MILLIS_PER_SECOND,
durationSec = durationMs.millisToSeconds(),
)
// Wire to Domain — for fresh server responses that bypass the cache
@@ -136,5 +138,3 @@ fun AlbumWire.toDomain(): AlbumRef =
durationSec = durationSec,
coverUrl = coverUrl,
)
private const val MILLIS_PER_SECOND = 1000
@@ -55,6 +55,7 @@ import com.fabledsword.minstrel.models.AlbumRef
import com.fabledsword.minstrel.models.TrackRef
import com.fabledsword.minstrel.nav.AlbumDetail
import com.fabledsword.minstrel.nav.ArtistDetail
import com.fabledsword.minstrel.shared.formatDuration
import com.fabledsword.minstrel.shared.widgets.CachedDot
import com.fabledsword.minstrel.shared.widgets.EmptyState
import com.fabledsword.minstrel.shared.widgets.LikeButton
@@ -352,7 +353,7 @@ private fun TrackRow(
}
CachedDot(track.id)
Text(
text = formatDuration(track.durationSec),
text = formatDuration(track.durationSec, zero = "--:--"),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 12.dp),
@@ -421,12 +422,3 @@ private fun SeededAlbumLoading(seed: AlbumRef) {
items(SKELETON_TRACK_ROWS) { SkeletonTrackRow() }
}
}
private const val SECONDS_PER_MINUTE = 60
private fun formatDuration(seconds: Int): String {
if (seconds <= 0) return "--:--"
val m = seconds / SECONDS_PER_MINUTE
val s = seconds % SECONDS_PER_MINUTE
return "$m:${s.toString().padStart(2, '0')}"
}
@@ -69,6 +69,8 @@ import com.fabledsword.minstrel.nav.LocalAnimatedContentScope
import com.fabledsword.minstrel.nav.LocalSharedTransitionScope
import com.fabledsword.minstrel.nav.NowPlaying
import com.fabledsword.minstrel.nav.Queue
import com.fabledsword.minstrel.shared.formatDuration
import com.fabledsword.minstrel.shared.millisToSeconds
import com.fabledsword.minstrel.shared.widgets.LikeButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
@@ -78,8 +80,6 @@ import com.fabledsword.minstrel.theme.LocalActionColors
private const val COVER_MAX_WIDTH_DP = 320
private const val TRANSPORT_ICON_DP = 36
private const val PLAY_PAUSE_ICON_DP = 56
private const val MS_PER_SECOND = 1000L
private const val SECONDS_PER_MINUTE = 60L
private const val POP_GRACE_MS = 500L
// Vertical drag-down threshold (in pixels) past which the gesture
@@ -420,12 +420,12 @@ private fun ScrubberRow(positionMs: Long, durationMs: Long, onSeek: (Long) -> Un
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = formatMillis(positionMs),
text = formatDuration(positionMs.millisToSeconds()),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = formatMillis(durationMs),
text = formatDuration(durationMs.millisToSeconds()),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
@@ -471,11 +471,3 @@ private fun TransportRow(
}
}
}
private fun formatMillis(ms: Long): String {
if (ms <= 0) return "0:00"
val totalSeconds = ms / MS_PER_SECOND
val minutes = totalSeconds / SECONDS_PER_MINUTE
val seconds = totalSeconds % SECONDS_PER_MINUTE
return "%d:%02d".format(minutes, seconds)
}
@@ -34,6 +34,7 @@ import com.composables.icons.lucide.ArrowLeft
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Volume2
import com.fabledsword.minstrel.models.TrackRef
import com.fabledsword.minstrel.shared.formatDuration
import com.fabledsword.minstrel.shared.widgets.EmptyState
@OptIn(ExperimentalMaterial3Api::class)
@@ -149,12 +150,4 @@ private fun queueSubtitle(track: TrackRef): String = listOf(track.artistName, tr
.filter { it.isNotEmpty() }
.joinToString(" · ")
private const val SECONDS_PER_MINUTE = 60
private fun formatDuration(seconds: Int): String {
val mins = seconds / SECONDS_PER_MINUTE
val secs = seconds % SECONDS_PER_MINUTE
return "%d:%02d".format(mins, secs)
}
private const val HIGHLIGHT_ALPHA = 0.12f
@@ -72,6 +72,7 @@ import com.fabledsword.minstrel.player.PlayerController
import com.fabledsword.minstrel.likes.data.LikesRepository
import com.fabledsword.minstrel.playlists.data.PlaylistDetailRef
import com.fabledsword.minstrel.playlists.data.PlaylistsRepository
import com.fabledsword.minstrel.shared.formatDuration
import com.fabledsword.minstrel.shared.widgets.CachedDot
import com.fabledsword.minstrel.shared.widgets.LikeButton
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
@@ -531,7 +532,7 @@ private fun TrackRow(
}
row.trackId?.let { CachedDot(it) }
Text(
text = formatDuration(row.durationSec),
text = formatDuration(row.durationSec, zero = "--:--"),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = rowAlpha),
modifier = Modifier.padding(start = 12.dp),
@@ -632,16 +633,8 @@ private fun ErrorBlock(message: String, onRetry: () -> Unit) {
// ─── Helpers ─────────────────────────────────────────────────────────
private const val SECONDS_PER_MINUTE = 60
private const val UNAVAILABLE_ALPHA = 0.4f
private fun formatDuration(seconds: Int): String {
if (seconds <= 0) return "--:--"
val m = seconds / SECONDS_PER_MINUTE
val s = seconds % SECONDS_PER_MINUTE
return "$m:${s.toString().padStart(2, '0')}"
}
/**
* Lossy mapping: PlaylistTrackRef → TrackRef so the player can take it.
* Caller must filter `isAvailable && streamUrl != null` first.
@@ -8,6 +8,7 @@ import com.fabledsword.minstrel.cache.mutations.MutationQueue
import com.fabledsword.minstrel.models.QuarantineRef
import com.fabledsword.minstrel.models.TrackRef
import com.fabledsword.minstrel.models.wire.QuarantineMineWire
import com.fabledsword.minstrel.shared.secondsToMillis
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.datetime.Clock
@@ -16,8 +17,6 @@ import retrofit2.create
import javax.inject.Inject
import javax.inject.Singleton
private const val MS_PER_SECOND = 1000
/**
* Outcome of an `unflag` call — mirrors LikesRepository.toggleLike's
* RequestOutcome flavor. ACCEPTED = server confirmed; QUEUED = the
@@ -142,7 +141,7 @@ private fun TrackRef.toQuarantineEntity(reason: String, notes: String): CachedQu
notes = notes.ifEmpty { null },
createdAt = Clock.System.now().toString(),
trackTitle = title,
trackDurationMs = durationSec * MS_PER_SECOND,
trackDurationMs = durationSec.secondsToMillis(),
albumId = albumId,
albumTitle = albumTitle,
albumCoverArtPath = null,