Optimize local time provision
This commit is contained in:
parent
6d15ab0e29
commit
e12bfb63fd
@ -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() }
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -45,8 +45,7 @@ fun DigitalClock2(
|
||||
else {
|
||||
"hh:mm:ss"
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (DateFormat.is24HourFormat(LocalContext.current)) {
|
||||
"HH:mm"
|
||||
}
|
||||
|
||||
@ -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<ZonedDateTime>(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
|
||||
|
||||
@ -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<ImageVector>()
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user