Optimize local time provision

This commit is contained in:
MM20 2024-04-05 18:33:21 +02:00
parent 6d15ab0e29
commit e12bfb63fd
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 297 additions and 326 deletions

View File

@ -1,85 +0,0 @@
package de.mm20.launcher2.ui.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
import kotlinx.coroutines.awaitCancellation
import org.koin.androidx.compose.inject
@Composable
fun ProvideClockTime(content: @Composable () -> Unit) {
val context = LocalContext.current
val clockSettings: ClockWidgetSettings by inject()
val showSeconds by clockSettings.showSeconds.collectAsState(initial = false)
val isCompact by clockSettings.compact.collectAsState(initial = false)
var time by remember { mutableStateOf(System.currentTimeMillis()) }
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(showSeconds, isCompact) {
time = System.currentTimeMillis()
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
time = System.currentTimeMillis()
val handler = Handler(Looper.myLooper()!!)
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
time = System.currentTimeMillis()
}
}
val callback = object : Runnable {
override fun run() {
time = System.currentTimeMillis()
handler.postDelayed(this, 1000 - (time % 1000))
}
}
if (!isCompact && showSeconds) {
context.registerReceiver(receiver, IntentFilter().apply {
addAction(Intent.ACTION_TIME_CHANGED)
})
handler.postDelayed(callback, 1000 - (time % 1000))
}
else {
context.registerReceiver(receiver, IntentFilter().apply {
addAction(Intent.ACTION_TIME_CHANGED)
addAction(Intent.ACTION_TIME_TICK)
})
}
try {
awaitCancellation()
} finally {
context.unregisterReceiver(receiver)
handler.removeCallbacks(callback)
}
}
}
CompositionLocalProvider(
LocalClockTime provides time,
content = content
)
}
val LocalClockTime = compositionLocalOf { System.currentTimeMillis() }

View File

@ -4,6 +4,10 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -17,7 +21,13 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import java.time.Instant
import java.time.ZoneId
/**
* Provide the current time (in millis) to the LocalTime composition local.
* The time is updated every second.
*/
@Composable @Composable
fun ProvideCurrentTime(content: @Composable () -> Unit) { fun ProvideCurrentTime(content: @Composable () -> Unit) {
@ -25,6 +35,17 @@ fun ProvideCurrentTime(content: @Composable () -> Unit) {
var time by remember { mutableStateOf(System.currentTimeMillis()) } var time by remember { mutableStateOf(System.currentTimeMillis()) }
val secondsAnimation = remember { Animatable(0f) }
LaunchedEffect(time) {
val currentSeconds = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).second
secondsAnimation.animateTo(currentSeconds.toFloat(), snap())
secondsAnimation.animateTo(
60f,
tween((60 - currentSeconds) * 1000, easing = LinearEasing)
)
}
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(null) { LaunchedEffect(null) {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
@ -50,7 +71,7 @@ fun ProvideCurrentTime(content: @Composable () -> Unit) {
} }
CompositionLocalProvider( CompositionLocalProvider(
LocalTime provides time, LocalTime provides time + secondsAnimation.value.toInt() * 1000,
content = content content = content
) )
} }

View File

@ -409,39 +409,11 @@ private fun ClockLayer(
tintColor: Color?, tintColor: Color?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val time = remember(LocalTime.current) { val time = Instant.ofEpochMilli(LocalTime.current).atZone(ZoneId.systemDefault())
Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault())
}
val second = remember { val second = time.second
Animatable((time.second).toFloat() + (time.nano / 1000000f) / 1000f) val minute = time.minute
} val hour = time.hour
val minute = remember {
Animatable((time.minute).toFloat() + time.second.toFloat() / 60f)
}
val hour = remember {
Animatable((time.hour).toFloat() + time.minute.toFloat() / 60f)
}
LaunchedEffect(time) {
val h = (time.hour).toFloat() + (time.minute).toFloat() / 60f
val m = (time.minute.toFloat() + (time.second).toFloat() / 60f)
val s = (time.second).toFloat() + (time.nano / 1000000f) / 1000f
second.snapTo(s)
hour.snapTo(h)
minute.snapTo(m)
launch {
hour.animateTo(h + 1.5f / 60f, tween(90000, easing = LinearEasing))
}
launch {
minute.animateTo(m + 1.5f, tween(90000, easing = LinearEasing))
}
launch {
second.animateTo(s + 90f, tween(90000, easing = LinearEasing))
}
}
Canvas(modifier = modifier) { Canvas(modifier = modifier) {
val colorFilter = tintColor?.let { val colorFilter = tintColor?.let {
@ -453,11 +425,11 @@ private fun ClockLayer(
for (sublayer in sublayers) { for (sublayer in sublayers) {
when (sublayer.role) { when (sublayer.role) {
ClockSublayerRole.Hour -> { ClockSublayerRole.Hour -> {
sublayer.drawable.level = (((hour.value.toInt() - defaultHour + 12) % 12) * 60 sublayer.drawable.level = (((hour - defaultHour + 12) % 12) * 60
+ ((minute.value.toInt()) % 60)) + ((minute) % 60))
} }
ClockSublayerRole.Minute -> sublayer.drawable.level = ((minute.value.toInt() - defaultMinute + 60) % 60) ClockSublayerRole.Minute -> sublayer.drawable.level = ((minute - defaultMinute + 60) % 60)
ClockSublayerRole.Second -> sublayer.drawable.level = (((second.value.toInt() - defaultSecond + 60) % 60) * 10) ClockSublayerRole.Second -> sublayer.drawable.level = (((second - defaultSecond + 60) % 60) * 10)
else -> {} else -> {}
} }
drawIntoCanvas { drawIntoCanvas {

View File

@ -68,8 +68,7 @@ import de.mm20.launcher2.preferences.ClockWidgetColors
import de.mm20.launcher2.preferences.ClockWidgetStyle import de.mm20.launcher2.preferences.ClockWidgetStyle
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.base.LocalClockTime import de.mm20.launcher2.ui.base.LocalTime
import de.mm20.launcher2.ui.base.ProvideClockTime
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@ -90,160 +89,158 @@ fun ClockWidget(
fillScreenHeight: Boolean, fillScreenHeight: Boolean,
editMode: Boolean = false, editMode: Boolean = false,
) { ) {
ProvideClockTime { val viewModel: ClockWidgetVM = viewModel()
val viewModel: ClockWidgetVM = viewModel() val context = LocalContext.current
val context = LocalContext.current val compact by viewModel.compactLayout.collectAsState()
val compact by viewModel.compactLayout.collectAsState() val clockStyle by viewModel.clockStyle.collectAsState()
val clockStyle by viewModel.clockStyle.collectAsState() val color by viewModel.color.collectAsState()
val color by viewModel.color.collectAsState() val alignment by viewModel.alignment.collectAsState()
val alignment by viewModel.alignment.collectAsState() val time = LocalTime.current
val time = LocalClockTime.current
val contentColor = val contentColor =
if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) { if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) {
Color(0, 0, 0, 180) Color(0, 0, 0, 180)
} else { } else {
Color.White Color.White
}
LaunchedEffect(time) {
viewModel.updateTime(time)
} }
val partProvider by remember { viewModel.getActivePart(context) }.collectAsStateWithLifecycle( LaunchedEffect(time) {
null viewModel.updateTime(time)
) }
AnimatedContent(editMode, label = "ClockWidget") { val partProvider by remember { viewModel.getActivePart(context) }.collectAsStateWithLifecycle(
if (it) { null
var configure by remember { mutableStateOf(false) } )
Column {
Surface( AnimatedContent(editMode, label = "ClockWidget") {
modifier = Modifier if (it) {
.fillMaxWidth() var configure by remember { mutableStateOf(false) }
.padding(vertical = 8.dp), Column {
shape = MaterialTheme.shapes.medium, Surface(
color = MaterialTheme.colorScheme.surfaceVariant, modifier = Modifier
shadowElevation = 2.dp, .fillMaxWidth()
tonalElevation = 2.dp, .padding(vertical = 8.dp),
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.surfaceVariant,
shadowElevation = 2.dp,
tonalElevation = 2.dp,
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Row( Box(modifier = Modifier.size(24.dp))
modifier = Modifier.padding(8.dp), Text(
verticalAlignment = Alignment.CenterVertically text = stringResource(id = R.string.preference_screen_clockwidget),
) { style = MaterialTheme.typography.titleMedium,
Box(modifier = Modifier.size(24.dp)) modifier = Modifier
Text( .weight(1f)
text = stringResource(id = R.string.preference_screen_clockwidget), .padding(horizontal = 8.dp),
style = MaterialTheme.typography.titleMedium, overflow = TextOverflow.Ellipsis,
modifier = Modifier maxLines = 1
.weight(1f) )
.padding(horizontal = 8.dp), IconButton(onClick = {
overflow = TextOverflow.Ellipsis, configure = true
maxLines = 1 }) {
Icon(
imageVector = Icons.Rounded.Tune,
contentDescription = stringResource(R.string.settings)
) )
IconButton(onClick = {
configure = true
}) {
Icon(
imageVector = Icons.Rounded.Tune,
contentDescription = stringResource(R.string.settings)
)
}
} }
} }
HorizontalDivider()
if (configure) {
ConfigureClockWidgetSheet(onDismiss = { configure = false })
}
} }
} else { HorizontalDivider()
Column(modifier = modifier) { if (configure) {
Box( ConfigureClockWidgetSheet(onDismiss = { configure = false })
modifier = Modifier }
.then(if (fillScreenHeight) Modifier.weight(1f) else Modifier) }
.fillMaxWidth(), } else {
contentAlignment = when (alignment) { Column(modifier = modifier) {
ClockWidgetAlignment.Center -> Alignment.Center Box(
ClockWidgetAlignment.Top -> Alignment.TopCenter modifier = Modifier
else -> Alignment.BottomCenter .then(if (fillScreenHeight) Modifier.weight(1f) else Modifier)
} .fillMaxWidth(),
contentAlignment = when (alignment) {
ClockWidgetAlignment.Center -> Alignment.Center
ClockWidgetAlignment.Top -> Alignment.TopCenter
else -> Alignment.BottomCenter
}
) {
CompositionLocalProvider(
LocalContentColor provides contentColor
) { ) {
CompositionLocalProvider( if (compact == false) {
LocalContentColor provides contentColor Column(
) { horizontalAlignment = Alignment.CenterHorizontally,
if (compact == false) { ) {
Column( Box(
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.clickable(
) { enabled = clockStyle !is ClockWidgetStyle.Empty,
Box( indication = null,
modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }
enabled = clockStyle !is ClockWidgetStyle.Empty,
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
viewModel.launchClockApp(context)
}
) { ) {
Clock(clockStyle, false) viewModel.launchClockApp(context)
} }
) {
Clock(clockStyle, false)
}
if (partProvider != null) { if (partProvider != null) {
DynamicZone( DynamicZone(
modifier = Modifier.padding(bottom = 8.dp), modifier = Modifier.padding(bottom = 8.dp),
compact = false, compact = false,
provider = partProvider, provider = partProvider,
) )
}
} }
} }
if (compact == true) { }
Row( if (compact == true) {
modifier = Modifier Row(
.fillMaxWidth() modifier = Modifier
.padding(end = 8.dp, bottom = 16.dp), .fillMaxWidth()
verticalAlignment = Alignment.CenterVertically, .padding(end = 8.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween verticalAlignment = Alignment.CenterVertically,
) { horizontalArrangement = Arrangement.SpaceBetween
if (partProvider != null) { ) {
DynamicZone( if (partProvider != null) {
modifier = Modifier.weight(1f), DynamicZone(
compact = true, modifier = Modifier.weight(1f),
provider = partProvider, compact = true,
) provider = partProvider,
}
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.height(56.dp)
.width(2.dp)
.background(
LocalContentColor.current
),
) )
Box( }
modifier = Modifier.clickable( Box(
enabled = clockStyle !is ClockWidgetStyle.Empty, modifier = Modifier
indication = null, .padding(horizontal = 16.dp)
interactionSource = remember { MutableInteractionSource() } .height(56.dp)
) { .width(2.dp)
viewModel.launchClockApp(context) .background(
} LocalContentColor.current
),
)
Box(
modifier = Modifier.clickable(
enabled = clockStyle !is ClockWidgetStyle.Empty,
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { ) {
Clock(clockStyle, true) viewModel.launchClockApp(context)
} }
) {
Clock(clockStyle, true)
} }
} }
} }
} }
val dockProvider by viewModel.dockProvider.collectAsState() }
if (dockProvider != null) { val dockProvider by viewModel.dockProvider.collectAsState()
Box( if (dockProvider != null) {
modifier = Modifier Box(
.fillMaxWidth() modifier = Modifier
.padding(bottom = 16.dp) .fillMaxWidth()
) { .padding(bottom = 16.dp)
dockProvider?.Component(false) ) {
} dockProvider?.Component(false)
} }
} }
} }
@ -256,18 +253,25 @@ fun Clock(
style: ClockWidgetStyle?, style: ClockWidgetStyle?,
compact: Boolean, compact: Boolean,
) { ) {
val time = LocalClockTime.current val time = LocalTime.current
val clockSettings: ClockWidgetSettings by inject() val clockSettings: ClockWidgetSettings by inject()
val showSeconds by clockSettings.showSeconds.collectAsState(initial = false) val showSeconds by clockSettings.showSeconds.collectAsState(initial = false)
val useThemeColor by clockSettings.useThemeColor.collectAsState(initial = false) val useThemeColor by clockSettings.useThemeColor.collectAsState(initial = false)
when (style) { when (style) {
is ClockWidgetStyle.Digital1 -> DigitalClock1(time, style, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Digital1 -> DigitalClock1(
time,
style,
compact,
showSeconds,
useThemeColor
)
is ClockWidgetStyle.Digital2 -> DigitalClock2(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Digital2 -> DigitalClock2(time, compact, showSeconds, useThemeColor)
is ClockWidgetStyle.Binary -> BinaryClock(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Binary -> BinaryClock(time, compact, showSeconds, useThemeColor)
is ClockWidgetStyle.Analog -> AnalogClock(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Analog -> AnalogClock(time, compact, showSeconds, useThemeColor)
is ClockWidgetStyle.Orbit -> OrbitClock(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Orbit -> OrbitClock(time, compact, showSeconds, useThemeColor)
is ClockWidgetStyle.Segment -> SegmentClock(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Segment -> SegmentClock(time, compact, showSeconds, useThemeColor)
is ClockWidgetStyle.Empty -> {} is ClockWidgetStyle.Empty -> {}
else -> {} else -> {}
} }
@ -355,16 +359,14 @@ fun ConfigureClockWidgetSheet(
val availableStyles by viewModel.availableClockStyles.collectAsState() val availableStyles by viewModel.availableClockStyles.collectAsState()
if (color != null && compact != null && availableStyles.isNotEmpty()) { if (color != null && compact != null && availableStyles.isNotEmpty()) {
ProvideClockTime { WatchFaceSelector(
WatchFaceSelector( styles = availableStyles,
styles = availableStyles, compact = compact!!,
compact = compact!!, colors = color!!,
colors = color!!, selected = style,
selected = style, onSelect = {
onSelect = { viewModel.setClockStyle(it)
viewModel.setClockStyle(it) })
})
}
} }
SingleChoiceSegmentedButtonRow( SingleChoiceSegmentedButtonRow(

View File

@ -45,8 +45,7 @@ fun DigitalClock2(
else { else {
"hh:mm:ss" "hh:mm:ss"
} }
} } else {
else {
if (DateFormat.is24HourFormat(LocalContext.current)) { if (DateFormat.is24HourFormat(LocalContext.current)) {
"HH:mm" "HH:mm"
} }

View File

@ -16,6 +16,7 @@ import androidx.compose.runtime.LaunchedEffect
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.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.center import androidx.compose.ui.geometry.center
@ -47,24 +48,18 @@ private val currentTime
@Composable @Composable
fun OrbitClock( fun OrbitClock(
_time: Long, time: Long,
compact: Boolean, compact: Boolean,
showSeconds: Boolean, showSeconds: Boolean,
useThemeColor: Boolean useThemeColor: Boolean
) { ) {
val verticalLayout = !compact val verticalLayout = !compact
val timeState = remember { mutableStateOf<ZonedDateTime>(currentTime) } val parsed = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault())
LaunchedEffect(_time) { val second = parsed.second
timeState.value = currentTime val minute = parsed.minute
} val hour = parsed.hour
val time by timeState
val second = time.second
val minute = time.minute
val hour = time.hour
val formattedHour = ( val formattedHour = (
if (DateFormat.is24HourFormat(LocalContext.current)) if (DateFormat.is24HourFormat(LocalContext.current))
hour hour

View File

@ -2,6 +2,17 @@ package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.StartOffsetType
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -12,8 +23,10 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
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.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.draw.alpha import androidx.compose.ui.draw.alpha
@ -28,6 +41,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import de.mm20.launcher2.ui.locals.LocalDarkTheme import de.mm20.launcher2.ui.locals.LocalDarkTheme
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.delay
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
@ -42,7 +56,14 @@ fun SegmentClock(
val hour = parsed.hour val hour = parsed.hour
val minute = parsed.minute val minute = parsed.minute
val second = parsed.second val second = parsed.second
val flick = remember { mutableStateOf(time % 1000 <= 500) }
var flick by remember { mutableStateOf(false) }
LaunchedEffect(second) {
flick = true
delay(500)
flick = false
}
val enabled = if (useThemeColor) { val enabled = if (useThemeColor) {
if (LocalContentColor.current == Color.White) { if (LocalContentColor.current == Color.White) {
@ -52,37 +73,11 @@ fun SegmentClock(
if (LocalDarkTheme.current) MaterialTheme.colorScheme.primaryContainer if (LocalDarkTheme.current) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primary
} }
} } else {
else {
LocalContentColor.current LocalContentColor.current
} }
val disabled = LocalContentColor.current val disabled = LocalContentColor.current
val owner = LocalLifecycleOwner.current
LaunchedEffect(null) {
owner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
val handler = Handler(Looper.myLooper()!!)
val callback = object : Runnable {
override fun run() {
val currentTime = System.currentTimeMillis()
flick.value = currentTime % 1000 <= 500
handler.postDelayed(this, (500 - (currentTime % 500)))
}
}
val currentTime = System.currentTimeMillis()
handler.postDelayed(callback, (500 - (currentTime % 500)))
try {
awaitCancellation()
}
finally {
handler.removeCallbacks(callback)
}
}
}
val allSegmentVectors = remember(compact, enabled, disabled) { val allSegmentVectors = remember(compact, enabled, disabled) {
val vectors = mutableListOf<ImageVector>() val vectors = mutableListOf<ImageVector>()
@ -98,9 +93,11 @@ fun SegmentClock(
} }
Row( Row(
modifier = Modifier.padding(top = if (!compact) 16.dp else 0.dp, modifier = Modifier.padding(
top = if (!compact) 16.dp else 0.dp,
bottom = if (!compact) 16.dp else 0.dp, bottom = if (!compact) 16.dp else 0.dp,
start = 0.dp, end = 0.dp), start = 0.dp, end = 0.dp
),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@ -109,7 +106,7 @@ fun SegmentClock(
Image(allSegmentVectors[hour % 10], null) Image(allSegmentVectors[hour % 10], null)
Separator(compact) Separator(compact)
Box(Modifier.alpha(if (flick.value) 1f else 0.05f)) { Image(separator, null) } Box(Modifier.alpha(if (flick) 1f else 0.05f)) { Image(separator, null) }
Separator(compact) Separator(compact)
Image(allSegmentVectors[minute / 10], null) Image(allSegmentVectors[minute / 10], null)
@ -118,7 +115,7 @@ fun SegmentClock(
if (!compact && showSeconds) { if (!compact && showSeconds) {
Separator(false) Separator(false)
Box(Modifier.alpha(if (flick.value) 1f else 0.05f)) { Image(separator, null) } Box(Modifier.alpha(if (flick) 1f else 0.05f)) { Image(separator, null) }
Separator(false) Separator(false)
Image(allSegmentVectors[second / 10], null) Image(allSegmentVectors[second / 10], null)
@ -140,9 +137,15 @@ private fun Separator(compact: Boolean) {
(E) (C) (E) (C)
(D) (D)
*/ */
private val segmentBitsForDigits = arrayOf(0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x00) private val segmentBitsForDigits =
arrayOf(0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x00)
private fun getVectorDigitForNumber(compact: Boolean, number: Int, enabled: Color, disabled: Color) : ImageVector { private fun getVectorDigitForNumber(
compact: Boolean,
number: Int,
enabled: Color,
disabled: Color
): ImageVector {
if (number < 0 || number > segmentBitsForDigits.size) { if (number < 0 || number > segmentBitsForDigits.size) {
throw IllegalArgumentException() throw IllegalArgumentException()
} }
@ -249,10 +252,10 @@ private fun getVectorDigitForNumber(compact: Boolean, number: Int, enabled: Colo
close() close()
} }
.build() .build()
} }
private fun getVectorSeparator(compact: Boolean, enabled: Color) : ImageVector { private fun getVectorSeparator(compact: Boolean, enabled: Color): ImageVector {
return ImageVector.Builder( return ImageVector.Builder(
defaultWidth = if (compact) 3.6.dp else 6.dp, defaultWidth = if (compact) 3.6.dp else 6.dp,
@ -266,16 +269,80 @@ private fun getVectorSeparator(compact: Boolean, enabled: Color) : ImageVector {
pathFillType = PathFillType.NonZero pathFillType = PathFillType.NonZero
) { ) {
moveTo(3.175f, 18.5f) moveTo(3.175f, 18.5f)
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, -1.587f, 1.588f) arcToRelative(
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, -1.588f, -1.588f) 1.587f,
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, 1.588f, -1.587f) 1.587f,
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, 1.587f, 1.587f) 0f,
isMoreThanHalf = false,
isPositiveArc = true,
-1.587f,
1.588f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
-1.588f,
-1.588f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
1.588f,
-1.587f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
1.587f,
1.587f
)
close() close()
moveToRelative(0f, -9.634f) moveToRelative(0f, -9.634f)
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, -1.587f, 1.588f) arcToRelative(
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, -1.588f, -1.588f) 1.587f,
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, 1.588f, -1.587f) 1.587f,
arcToRelative(1.587f, 1.587f, 0f, isMoreThanHalf = false, isPositiveArc = true, 1.587f, 1.587f) 0f,
isMoreThanHalf = false,
isPositiveArc = true,
-1.587f,
1.588f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
-1.588f,
-1.588f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
1.588f,
-1.587f
)
arcToRelative(
1.587f,
1.587f,
0f,
isMoreThanHalf = false,
isPositiveArc = true,
1.587f,
1.587f
)
close() close()
} }
}.build() }.build()