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.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
)
}

View File

@ -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 {

View File

@ -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(

View File

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

View File

@ -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

View File

@ -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()