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.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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user