From e12bfb63fd20c9e68925fddb5ec7ff49f95125ee Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Fri, 5 Apr 2024 18:33:21 +0200 Subject: [PATCH] Optimize local time provision --- .../launcher2/ui/base/ProvideClockTime.kt | 85 ----- .../launcher2/ui/base/ProvideCurrentTime.kt | 23 +- .../ui/component/ShapedLauncherIcon.kt | 44 +-- .../ui/launcher/widgets/clock/ClockWidget.kt | 296 +++++++++--------- .../widgets/clock/clocks/DigitalClock2.kt | 3 +- .../widgets/clock/clocks/OrbitClock.kt | 17 +- .../widgets/clock/clocks/SegmentClock.kt | 155 ++++++--- 7 files changed, 297 insertions(+), 326 deletions(-) delete mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideClockTime.kt diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideClockTime.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideClockTime.kt deleted file mode 100644 index 8df012f5..00000000 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideClockTime.kt +++ /dev/null @@ -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() } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideCurrentTime.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideCurrentTime.kt index 08c96748..2f0e1789 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideCurrentTime.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideCurrentTime.kt @@ -4,6 +4,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent 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.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -17,7 +21,13 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle 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 fun ProvideCurrentTime(content: @Composable () -> Unit) { @@ -25,6 +35,17 @@ fun ProvideCurrentTime(content: @Composable () -> Unit) { 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 LaunchedEffect(null) { lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { @@ -50,7 +71,7 @@ fun ProvideCurrentTime(content: @Composable () -> Unit) { } CompositionLocalProvider( - LocalTime provides time, + LocalTime provides time + secondsAnimation.value.toInt() * 1000, content = content ) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt index 7fc45136..d8cc60a6 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt @@ -409,39 +409,11 @@ private fun ClockLayer( tintColor: Color?, modifier: Modifier = Modifier, ) { - val time = remember(LocalTime.current) { - Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault()) - } + val time = Instant.ofEpochMilli(LocalTime.current).atZone(ZoneId.systemDefault()) - val second = remember { - Animatable((time.second).toFloat() + (time.nano / 1000000f) / 1000f) - } - - 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)) - } - } + val second = time.second + val minute = time.minute + val hour = time.hour Canvas(modifier = modifier) { val colorFilter = tintColor?.let { @@ -453,11 +425,11 @@ private fun ClockLayer( for (sublayer in sublayers) { when (sublayer.role) { ClockSublayerRole.Hour -> { - sublayer.drawable.level = (((hour.value.toInt() - defaultHour + 12) % 12) * 60 - + ((minute.value.toInt()) % 60)) + sublayer.drawable.level = (((hour - defaultHour + 12) % 12) * 60 + + ((minute) % 60)) } - ClockSublayerRole.Minute -> sublayer.drawable.level = ((minute.value.toInt() - defaultMinute + 60) % 60) - ClockSublayerRole.Second -> sublayer.drawable.level = (((second.value.toInt() - defaultSecond + 60) % 60) * 10) + ClockSublayerRole.Minute -> sublayer.drawable.level = ((minute - defaultMinute + 60) % 60) + ClockSublayerRole.Second -> sublayer.drawable.level = (((second - defaultSecond + 60) % 60) * 10) else -> {} } drawIntoCanvas { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index 73717724..b0bdeb99 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -68,8 +68,7 @@ import de.mm20.launcher2.preferences.ClockWidgetColors import de.mm20.launcher2.preferences.ClockWidgetStyle import de.mm20.launcher2.preferences.ui.ClockWidgetSettings import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.base.LocalClockTime -import de.mm20.launcher2.ui.base.ProvideClockTime +import de.mm20.launcher2.ui.base.LocalTime import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.SwitchPreference @@ -90,160 +89,158 @@ fun ClockWidget( fillScreenHeight: Boolean, editMode: Boolean = false, ) { - ProvideClockTime { - val viewModel: ClockWidgetVM = viewModel() - val context = LocalContext.current - val compact by viewModel.compactLayout.collectAsState() - val clockStyle by viewModel.clockStyle.collectAsState() - val color by viewModel.color.collectAsState() - val alignment by viewModel.alignment.collectAsState() - val time = LocalClockTime.current + val viewModel: ClockWidgetVM = viewModel() + val context = LocalContext.current + val compact by viewModel.compactLayout.collectAsState() + val clockStyle by viewModel.clockStyle.collectAsState() + val color by viewModel.color.collectAsState() + val alignment by viewModel.alignment.collectAsState() + val time = LocalTime.current - val contentColor = - if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) { - Color(0, 0, 0, 180) - } else { - Color.White - } - - LaunchedEffect(time) { - viewModel.updateTime(time) + val contentColor = + if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) { + Color(0, 0, 0, 180) + } else { + Color.White } - val partProvider by remember { viewModel.getActivePart(context) }.collectAsStateWithLifecycle( - null - ) + LaunchedEffect(time) { + viewModel.updateTime(time) + } - AnimatedContent(editMode, label = "ClockWidget") { - if (it) { - var configure by remember { mutableStateOf(false) } - Column { - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - shape = MaterialTheme.shapes.medium, - color = MaterialTheme.colorScheme.surfaceVariant, - shadowElevation = 2.dp, - tonalElevation = 2.dp, + val partProvider by remember { viewModel.getActivePart(context) }.collectAsStateWithLifecycle( + null + ) + + AnimatedContent(editMode, label = "ClockWidget") { + if (it) { + var configure by remember { mutableStateOf(false) } + Column { + Surface( + modifier = Modifier + .fillMaxWidth() + .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( - modifier = Modifier.padding(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Box(modifier = Modifier.size(24.dp)) - Text( - text = stringResource(id = R.string.preference_screen_clockwidget), - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .weight(1f) - .padding(horizontal = 8.dp), - overflow = TextOverflow.Ellipsis, - maxLines = 1 + Box(modifier = Modifier.size(24.dp)) + Text( + text = stringResource(id = R.string.preference_screen_clockwidget), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier + .weight(1f) + .padding(horizontal = 8.dp), + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + IconButton(onClick = { + configure = true + }) { + 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 { - Column(modifier = modifier) { - Box( - modifier = Modifier - .then(if (fillScreenHeight) Modifier.weight(1f) else Modifier) - .fillMaxWidth(), - contentAlignment = when (alignment) { - ClockWidgetAlignment.Center -> Alignment.Center - ClockWidgetAlignment.Top -> Alignment.TopCenter - else -> Alignment.BottomCenter - } + HorizontalDivider() + if (configure) { + ConfigureClockWidgetSheet(onDismiss = { configure = false }) + } + } + } else { + Column(modifier = modifier) { + Box( + modifier = Modifier + .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( - LocalContentColor provides contentColor - ) { - if (compact == false) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Box( - modifier = Modifier.clickable( - enabled = clockStyle !is ClockWidgetStyle.Empty, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - viewModel.launchClockApp(context) - } + if (compact == false) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier.clickable( + enabled = clockStyle !is ClockWidgetStyle.Empty, + indication = null, + interactionSource = remember { MutableInteractionSource() } ) { - Clock(clockStyle, false) + viewModel.launchClockApp(context) } + ) { + Clock(clockStyle, false) + } - if (partProvider != null) { - DynamicZone( - modifier = Modifier.padding(bottom = 8.dp), - compact = false, - provider = partProvider, - ) - } + if (partProvider != null) { + DynamicZone( + modifier = Modifier.padding(bottom = 8.dp), + compact = false, + provider = partProvider, + ) } } - if (compact == true) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(end = 8.dp, bottom = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - if (partProvider != null) { - DynamicZone( - modifier = Modifier.weight(1f), - compact = true, - provider = partProvider, - ) - } - Box( - modifier = Modifier - .padding(horizontal = 16.dp) - .height(56.dp) - .width(2.dp) - .background( - LocalContentColor.current - ), + } + if (compact == true) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = 8.dp, bottom = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + if (partProvider != null) { + DynamicZone( + modifier = Modifier.weight(1f), + compact = true, + provider = partProvider, ) - Box( - modifier = Modifier.clickable( - enabled = clockStyle !is ClockWidgetStyle.Empty, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - viewModel.launchClockApp(context) - } + } + Box( + modifier = Modifier + .padding(horizontal = 16.dp) + .height(56.dp) + .width(2.dp) + .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) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp) - ) { - dockProvider?.Component(false) - } + } + val dockProvider by viewModel.dockProvider.collectAsState() + if (dockProvider != null) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) { + dockProvider?.Component(false) } } } @@ -256,18 +253,25 @@ fun Clock( style: ClockWidgetStyle?, compact: Boolean, ) { - val time = LocalClockTime.current + val time = LocalTime.current val clockSettings: ClockWidgetSettings by inject() val showSeconds by clockSettings.showSeconds.collectAsState(initial = false) val useThemeColor by clockSettings.useThemeColor.collectAsState(initial = false) 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.Binary -> BinaryClock(time, compact, showSeconds, useThemeColor) is ClockWidgetStyle.Analog -> AnalogClock(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 -> {} else -> {} } @@ -355,16 +359,14 @@ fun ConfigureClockWidgetSheet( val availableStyles by viewModel.availableClockStyles.collectAsState() if (color != null && compact != null && availableStyles.isNotEmpty()) { - ProvideClockTime { - WatchFaceSelector( - styles = availableStyles, - compact = compact!!, - colors = color!!, - selected = style, - onSelect = { - viewModel.setClockStyle(it) - }) - } + WatchFaceSelector( + styles = availableStyles, + compact = compact!!, + colors = color!!, + selected = style, + onSelect = { + viewModel.setClockStyle(it) + }) } SingleChoiceSegmentedButtonRow( diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt index dfe117e4..6ee4d5fb 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt @@ -45,8 +45,7 @@ fun DigitalClock2( else { "hh:mm:ss" } - } - else { + } else { if (DateFormat.is24HourFormat(LocalContext.current)) { "HH:mm" } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt index c40a6566..2a4fffdd 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.center @@ -47,24 +48,18 @@ private val currentTime @Composable fun OrbitClock( - _time: Long, + time: Long, compact: Boolean, showSeconds: Boolean, useThemeColor: Boolean ) { val verticalLayout = !compact - val timeState = remember { mutableStateOf(currentTime) } + val parsed = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()) - LaunchedEffect(_time) { - timeState.value = currentTime - } - - val time by timeState - - val second = time.second - val minute = time.minute - val hour = time.hour + val second = parsed.second + val minute = parsed.minute + val hour = parsed.hour val formattedHour = ( if (DateFormat.is24HourFormat(LocalContext.current)) hour diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt index 8a418570..5d500ffd 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt @@ -2,6 +2,17 @@ package de.mm20.launcher2.ui.launcher.widgets.clock.clocks import android.os.Handler 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.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -12,8 +23,10 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -28,6 +41,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import de.mm20.launcher2.ui.locals.LocalDarkTheme import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.delay import java.time.Instant import java.time.ZoneId @@ -42,7 +56,14 @@ fun SegmentClock( val hour = parsed.hour val minute = parsed.minute 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) { if (LocalContentColor.current == Color.White) { @@ -52,37 +73,11 @@ fun SegmentClock( if (LocalDarkTheme.current) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.primary } - } - else { + } else { 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 vectors = mutableListOf() @@ -98,9 +93,11 @@ fun SegmentClock( } 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, - start = 0.dp, end = 0.dp), + start = 0.dp, end = 0.dp + ), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { @@ -109,7 +106,7 @@ fun SegmentClock( Image(allSegmentVectors[hour % 10], null) 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) Image(allSegmentVectors[minute / 10], null) @@ -118,7 +115,7 @@ fun SegmentClock( if (!compact && showSeconds) { 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) Image(allSegmentVectors[second / 10], null) @@ -140,9 +137,15 @@ private fun Separator(compact: Boolean) { (E) (C) └─(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) { throw IllegalArgumentException() } @@ -249,10 +252,10 @@ private fun getVectorDigitForNumber(compact: Boolean, number: Int, enabled: Colo close() } - .build() + .build() } -private fun getVectorSeparator(compact: Boolean, enabled: Color) : ImageVector { +private fun getVectorSeparator(compact: Boolean, enabled: Color): ImageVector { return ImageVector.Builder( defaultWidth = if (compact) 3.6.dp else 6.dp, @@ -266,16 +269,80 @@ private fun getVectorSeparator(compact: Boolean, enabled: Color) : ImageVector { pathFillType = PathFillType.NonZero ) { moveTo(3.175f, 18.5f) - arcToRelative(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) + arcToRelative( + 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() moveToRelative(0f, -9.634f) - arcToRelative(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) + arcToRelative( + 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() } }.build()