Add clock widget customizations
This commit is contained in:
parent
eebffa333e
commit
ed11330674
@ -404,7 +404,6 @@
|
||||
<string name="preference_about_license_summary">Lizenziert unter der GNU General Public License 3.0</string>
|
||||
<string name="feature_not_available">Diese Funktion ist in dieser Version von %1$s nicht verfügbar.</string>
|
||||
<string name="preference_category_clock_widget">Uhr</string>
|
||||
<string name="preference_clock_widget_style">Uhrenstil</string>
|
||||
<plurals name="calendar_widget_running_events">
|
||||
<item quantity="one">+%1$d laufender Termin aus vergangenen Tagen</item>
|
||||
<item quantity="other">+%1$d laufende Termine aus vergangenen Tagen</item>
|
||||
@ -432,6 +431,13 @@
|
||||
<string name="preference_screen_weatherwidget">Wetter</string>
|
||||
<string name="preference_screen_musicwidget">Musik</string>
|
||||
|
||||
<string name="preference_screen_clockwidget">Uhr</string>
|
||||
<string name="preference_clockwidget_layout">Layout</string>
|
||||
<string name="preference_clockwidget_layout_vertical">Vertikal</string>
|
||||
<string name="preference_clockwidget_layout_horizontal">Horizontal</string>
|
||||
<string name="preference_clock_widget_style">Stil</string>
|
||||
<string name="preference_clock_widget_style_summary">Uhr auswählen</string>
|
||||
|
||||
<string name="preference_crash_reporter">Crash-Reporter</string>
|
||||
<string name="preference_crash_reporter_summary">Fehler- und Absturzberichte</string>
|
||||
|
||||
|
||||
@ -451,7 +451,6 @@
|
||||
<string name="preference_about_license_summary">Licensed under the GNU General Public License 3.0</string>
|
||||
<string name="feature_not_available">This feature is not available in this version of %1$s</string>
|
||||
<string name="preference_category_clock_widget">Clock</string>
|
||||
<string name="preference_clock_widget_style">Clock style</string>
|
||||
|
||||
<string name="preference_category_grid">Grid</string>
|
||||
<string name="preference_grid_column_count">Number of columns</string>
|
||||
@ -470,6 +469,13 @@
|
||||
<string name="preference_screen_weatherwidget">Weather</string>
|
||||
<string name="preference_screen_musicwidget">Music</string>
|
||||
|
||||
<string name="preference_screen_clockwidget">Clock</string>
|
||||
<string name="preference_clockwidget_layout">Layout</string>
|
||||
<string name="preference_clockwidget_layout_vertical">Vertical</string>
|
||||
<string name="preference_clockwidget_layout_horizontal">Horizontal</string>
|
||||
<string name="preference_clock_widget_style">Style</string>
|
||||
<string name="preference_clock_widget_style_summary">Select a clock</string>
|
||||
|
||||
<string name="preference_crash_reporter">Crash reporter</string>
|
||||
<string name="preference_crash_reporter_summary">Error and crash reports</string>
|
||||
|
||||
|
||||
@ -21,5 +21,11 @@ fun createFactorySettings(context: Context): Settings {
|
||||
.setFilterSources(true)
|
||||
.build()
|
||||
)
|
||||
.setClockWidget(Settings.ClockWidgetSettings
|
||||
.newBuilder()
|
||||
.setLayout(Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical)
|
||||
.setClockStyle(Settings.ClockWidgetSettings.ClockStyle.DigitalClock1)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
@ -37,4 +37,19 @@ message Settings {
|
||||
}
|
||||
MusicWidgetSettings music_widget = 6;
|
||||
|
||||
message ClockWidgetSettings {
|
||||
enum ClockWidgetLayout {
|
||||
Vertical = 0;
|
||||
Horizontal = 1;
|
||||
}
|
||||
ClockWidgetLayout layout = 1;
|
||||
enum ClockStyle {
|
||||
DigitalClock1 = 0;
|
||||
DigitalClock2 = 1;
|
||||
BinaryClock = 2;
|
||||
}
|
||||
ClockStyle clock_style = 2;
|
||||
}
|
||||
ClockWidgetSettings clock_widget = 7;
|
||||
|
||||
}
|
||||
@ -137,6 +137,9 @@ dependencyResolutionManagement {
|
||||
alias("accompanist.pager")
|
||||
.to("com.google.accompanist", "accompanist-pager")
|
||||
.versionRef("accompanist")
|
||||
alias("accompanist.pagerindicators")
|
||||
.to("com.google.accompanist", "accompanist-pager-indicators")
|
||||
.versionRef("accompanist")
|
||||
alias("accompanist.flowlayout")
|
||||
.to("com.google.accompanist", "accompanist-flowlayout")
|
||||
.versionRef("accompanist")
|
||||
|
||||
@ -84,6 +84,7 @@ dependencies {
|
||||
implementation(libs.accompanist.insets)
|
||||
implementation(libs.accompanist.systemuicontroller)
|
||||
implementation(libs.accompanist.pager)
|
||||
implementation(libs.accompanist.pagerindicators)
|
||||
implementation(libs.accompanist.flowlayout)
|
||||
implementation(libs.accompanist.navigationanimation)
|
||||
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
package de.mm20.launcher2.ui.component
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PointMode
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import de.mm20.launcher2.ui.locals.LocalColorScheme
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun DigitalClock(time: Long) {
|
||||
val format = SimpleDateFormat(
|
||||
if (DateFormat.is24HourFormat(LocalContext.current))
|
||||
"HH\nmm" else "hh\nmm",
|
||||
Locale.getDefault()
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.offset(y = 16.dp),
|
||||
text = format.format(time),
|
||||
style = MaterialTheme.typography.displayLarge.copy(
|
||||
fontSize = 100.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 0.8.em,
|
||||
letterSpacing = -0.1.em,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BinaryClock(time: Long) {
|
||||
val date = Calendar.getInstance()
|
||||
date.timeInMillis = time
|
||||
val minute = date[Calendar.MINUTE]
|
||||
var hour = date[Calendar.HOUR]
|
||||
if (hour == 0) hour = 12
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 24.dp)
|
||||
) {
|
||||
for (i in 0 until 10) {
|
||||
val active = if (i < 4) {
|
||||
hour and (1 shl (3 - i)) != 0
|
||||
} else {
|
||||
minute and (1 shl (9 - i)) != 0
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(12.dp)
|
||||
.background(
|
||||
LocalContentColor.current.copy(
|
||||
if (active) 1f else 0.45f
|
||||
)
|
||||
)
|
||||
)
|
||||
if (i == 3) {
|
||||
Box(Modifier.size(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnalogClock(time: Long) {
|
||||
val date = Calendar.getInstance()
|
||||
date.timeInMillis = time
|
||||
val minute = date[Calendar.MINUTE]
|
||||
val hour = date[Calendar.HOUR]
|
||||
val dark = true//!MaterialTheme.colors.isLight
|
||||
val cs = LocalColorScheme.current
|
||||
val bgColor = if (dark) cs.primary.shade20 else cs.primary.shade80
|
||||
val hourColor = if (dark) cs.primary.shade70 else cs.primary.shade40
|
||||
val minuteColor = if (dark) cs.primary.shade80 else cs.primary.shade30
|
||||
val textColor = if (dark) cs.primary.shade50 else cs.primary.shade60
|
||||
|
||||
val hourAngle = 30f * hour + 0.5f * minute
|
||||
val minuteAngle = 6f * minute
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 24.dp)
|
||||
.size(156.dp),
|
||||
shape = CircleShape,
|
||||
color = bgColor,
|
||||
shadowElevation = 8.dp
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "12",
|
||||
style = MaterialTheme.typography.headlineMedium.copy(
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 32.sp,
|
||||
),
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 2.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
)
|
||||
Text(
|
||||
text = "3",
|
||||
style = MaterialTheme.typography.headlineMedium.copy(
|
||||
fontSize = 32.sp
|
||||
),
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 2.dp)
|
||||
.align(Alignment.CenterEnd),
|
||||
)
|
||||
Text(
|
||||
text = "6",
|
||||
style = MaterialTheme.typography.headlineMedium.copy(
|
||||
fontSize = 32.sp
|
||||
),
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 2.dp)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
Text(
|
||||
text = "9",
|
||||
style = MaterialTheme.typography.headlineMedium.copy(
|
||||
fontSize = 32.sp
|
||||
),
|
||||
color = textColor,
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 2.dp)
|
||||
.align(Alignment.CenterStart),
|
||||
)
|
||||
}
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
rotate(
|
||||
degrees = hourAngle - 180f
|
||||
) {
|
||||
|
||||
drawLine(
|
||||
strokeWidth = 12.dp.toPx(),
|
||||
start = size.center.plus(Offset(0f, size.width / 5f)),
|
||||
end = size.center,
|
||||
color = hourColor,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
rotate(
|
||||
degrees = minuteAngle - 180f
|
||||
) {
|
||||
drawLine(
|
||||
strokeWidth = 12.dp.toPx(),
|
||||
start = size.center.plus(Offset(0f, size.width / 3f)),
|
||||
end = size.center,
|
||||
color = minuteColor,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PointMode.Polygon
|
||||
}
|
||||
@ -1,87 +1,138 @@
|
||||
package de.mm20.launcher2.ui
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.provider.AlarmClock
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ExpandLess
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.component.DigitalClock
|
||||
import de.mm20.launcher2.ui.widget.parts.DatePart
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockStyle
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidgetVM
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.BinaryClock
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock1
|
||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock2
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.parts.DatePart
|
||||
|
||||
@Composable
|
||||
fun ClockWidget(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val viewModel: ClockWidgetVM = viewModel()
|
||||
val context = LocalContext.current
|
||||
val time by viewModel.getTime(context).collectAsState(System.currentTimeMillis())
|
||||
val layout by viewModel.layout.observeAsState()
|
||||
val clockStyle by viewModel.clockStyle.observeAsState()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
|
||||
CompositionLocalProvider(LocalContentColor provides Color.White) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.height(IntrinsicSize.Min)
|
||||
) {
|
||||
Clock()
|
||||
|
||||
DynamicZone()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Clock() {
|
||||
var time by remember { mutableStateOf(System.currentTimeMillis()) }
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(null) {
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
time = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(Intent.ACTION_TIME_TICK).also {
|
||||
it.addAction(Intent.ACTION_TIME_CHANGED)
|
||||
it.addAction(Intent.ACTION_TIMEZONE_CHANGED)
|
||||
}
|
||||
context.registerReceiver(receiver, filter)
|
||||
onDispose {
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides Color.White
|
||||
) {
|
||||
context.startActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
if (layout == ClockWidgetLayout.Vertical) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.height(IntrinsicSize.Min),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
viewModel.launchClockApp(context)
|
||||
}
|
||||
) {
|
||||
Clock(clockStyle, ClockWidgetLayout.Vertical, time)
|
||||
}
|
||||
|
||||
DynamicZone(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
ClockWidgetLayout.Vertical,
|
||||
time
|
||||
)
|
||||
}
|
||||
}
|
||||
if (layout == ClockWidgetLayout.Horizontal) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(imageVector = Icons.Rounded.ExpandLess, contentDescription = "")
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
DynamicZone(
|
||||
modifier = Modifier.weight(1f),
|
||||
ClockWidgetLayout.Horizontal,
|
||||
time
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(56.dp)
|
||||
.width(2.dp)
|
||||
.background(
|
||||
LocalContentColor.current
|
||||
),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
viewModel.launchClockApp(context)
|
||||
}
|
||||
) {
|
||||
Clock(clockStyle, ClockWidgetLayout.Horizontal, time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
DigitalClock(time = time)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DynamicZone() {
|
||||
fun Clock(
|
||||
style: ClockStyle?,
|
||||
layout: ClockWidgetLayout,
|
||||
time: Long
|
||||
) {
|
||||
when (style) {
|
||||
ClockStyle.DigitalClock1 -> DigitalClock1(time, layout)
|
||||
ClockStyle.DigitalClock2 -> DigitalClock2(time, layout)
|
||||
ClockStyle.BinaryClock -> BinaryClock(time, layout)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DynamicZone(
|
||||
modifier: Modifier = Modifier,
|
||||
layout: ClockWidgetLayout,
|
||||
time: Long,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
modifier = modifier
|
||||
) {
|
||||
DatePart()
|
||||
DatePart(time, layout)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,49 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.provider.AlarmClock
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.lifecycle.BroadcastReceiverLiveData
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class ClockWidgetVM: ViewModel() {
|
||||
class ClockWidgetVM: ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
val layout = dataStore.data.map { it.clockWidget.layout }.asLiveData()
|
||||
val clockStyle = dataStore.data.map { it.clockWidget.clockStyle }.asLiveData()
|
||||
|
||||
fun getTime(context: Context): Flow<Long> = callbackFlow {
|
||||
trySendBlocking(System.currentTimeMillis())
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
trySendBlocking(System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
context.registerReceiver(receiver, IntentFilter().apply {
|
||||
addAction(Intent.ACTION_TIME_TICK)
|
||||
addAction(Intent.ACTION_TIME_CHANGED)
|
||||
})
|
||||
awaitClose {
|
||||
context.unregisterReceiver(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
fun launchClockApp(context: Context) {
|
||||
context.tryStartActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun BinaryClock(
|
||||
time: Long,
|
||||
layout: Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
) {
|
||||
val verticalLayout = layout == Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical
|
||||
val date = Calendar.getInstance()
|
||||
date.timeInMillis = time
|
||||
val minute = date[Calendar.MINUTE]
|
||||
var hour = date[Calendar.HOUR]
|
||||
if (hour == 0) hour = 12
|
||||
if (verticalLayout) {
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 24.dp)
|
||||
) {
|
||||
for (i in 0 until 10) {
|
||||
val active = if (i < 4) {
|
||||
hour and (1 shl (3 - i)) != 0
|
||||
} else {
|
||||
minute and (1 shl (9 - i)) != 0
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(12.dp)
|
||||
.background(
|
||||
LocalContentColor.current.copy(
|
||||
if (active) 1f else 0.45f
|
||||
)
|
||||
)
|
||||
)
|
||||
if (i == 3) {
|
||||
Box(Modifier.size(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Row {
|
||||
for (i in 0 until 4) {
|
||||
val active = hour and (1 shl (3 - i)) != 0
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding( 4.dp)
|
||||
.size(12.dp)
|
||||
.background(
|
||||
LocalContentColor.current.copy(
|
||||
if (active) 1f else 0.45f
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Row {
|
||||
for (i in 4 until 10) {
|
||||
val active = minute and (1 shl (9 - i)) != 0
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.size(12.dp)
|
||||
.background(
|
||||
LocalContentColor.current.copy(
|
||||
if (active) 1f else 0.45f
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import androidx.compose.ui.unit.sp
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun DigitalClock1(
|
||||
time: Long,
|
||||
layout: Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
) {
|
||||
val verticalLayout = layout == Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical
|
||||
val format = SimpleDateFormat(
|
||||
if (verticalLayout) {
|
||||
if (DateFormat.is24HourFormat(LocalContext.current)) "HH\nmm" else "hh\nmm"
|
||||
} else {
|
||||
if (DateFormat.is24HourFormat(LocalContext.current)) "HH mm" else "hh mm"
|
||||
},
|
||||
Locale.getDefault()
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.offset(y = if (verticalLayout) 16.dp else 0.dp),
|
||||
text = format.format(time),
|
||||
style = MaterialTheme.typography.displayLarge.copy(
|
||||
fontSize = if (verticalLayout) 100.sp else 48.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 0.8.em,
|
||||
letterSpacing = -0.1.em,
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
|
||||
|
||||
@Composable
|
||||
fun DigitalClock2(
|
||||
time: Long,
|
||||
layout: Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
) {
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(LocalContext.current, time, DateUtils.FORMAT_SHOW_TIME),
|
||||
style = MaterialTheme.typography.displayLarge
|
||||
)
|
||||
}
|
||||
@ -108,7 +108,6 @@ class LauncherActivity : BaseActivity() {
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
binding.widgetSpacer.visibility = View.GONE
|
||||
binding.clockWidget.visibility = View.GONE
|
||||
binding.searchBar.setRightIcon(R.drawable.ic_done)
|
||||
binding.scrollView.setOnTouchListener(null)
|
||||
@ -154,7 +153,6 @@ class LauncherActivity : BaseActivity() {
|
||||
.start()
|
||||
} else {
|
||||
widgetViewModel.saveWidgets(widgets)
|
||||
binding.widgetSpacer.visibility = View.VISIBLE
|
||||
binding.widgetList.layoutTransition = ChangingLayoutTransition()
|
||||
binding.widgetContainer.layoutTransition = ChangingLayoutTransition()
|
||||
binding.scrollContainer.layoutTransition = ChangingLayoutTransition()
|
||||
@ -226,9 +224,9 @@ class LauncherActivity : BaseActivity() {
|
||||
binding.searchContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
binding.widgetContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
|
||||
val params = binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams
|
||||
params.topMargin = Point().also { windowManager.defaultDisplay.getSize(it) }.y
|
||||
binding.widgetSpacer.layoutParams = params
|
||||
val params = binding.clockWidget.layoutParams
|
||||
params.height = Point().also { windowManager.defaultDisplay.getSize(it) }.y
|
||||
binding.clockWidget.layoutParams = params
|
||||
binding.container.doOnLayout {
|
||||
adjustWidgetSpace()
|
||||
}
|
||||
@ -237,9 +235,7 @@ class LauncherActivity : BaseActivity() {
|
||||
binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
|
||||
when {
|
||||
/* Hide searchbar*/
|
||||
scrollY > oldScrollY && ((searchVisibility && scrollY > binding.searchBar.height) || widgetEditMode ||
|
||||
scrollY > binding.widgetSpacer.height + binding.searchBar.height
|
||||
+ (binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams).topMargin) -> {
|
||||
scrollY > oldScrollY && ((scrollY > binding.searchBar.height) || widgetEditMode) -> {
|
||||
var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
|
||||
if (newTransY < -binding.searchBar.height.toFloat() * 1.5f) {
|
||||
newTransY = -binding.searchBar.height.toFloat() * 1.5f
|
||||
@ -255,9 +251,7 @@ class LauncherActivity : BaseActivity() {
|
||||
binding.searchBar.translationY = newTransY
|
||||
}
|
||||
}
|
||||
if (scrollY > 0 && (searchVisibility || widgetEditMode ||
|
||||
scrollY > binding.widgetSpacer.height
|
||||
+ (binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams).topMargin)
|
||||
if (scrollY > 0 && (searchVisibility || widgetEditMode)
|
||||
) {
|
||||
binding.searchBar.raise()
|
||||
} else binding.searchBar.drop()
|
||||
@ -455,13 +449,10 @@ class LauncherActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun adjustWidgetSpace() {
|
||||
val firstWidget = binding.clockWidget
|
||||
val m = binding.scrollContainer.paddingTop +
|
||||
(firstWidget.layoutParams as LinearLayout.LayoutParams).run { topMargin + bottomMargin }
|
||||
val params = binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams
|
||||
params.topMargin =
|
||||
binding.scrollView.height - firstWidget.measuredHeight - m - binding.widgetContainer.paddingTop - binding.widgetSpacer.height
|
||||
binding.widgetSpacer.layoutParams = params
|
||||
val height = binding.scrollView.height - binding.searchBar.height - 8 * dp
|
||||
binding.clockWidget.layoutParams = binding.clockWidget.layoutParams.also {
|
||||
it.height = height.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ class ClockWidget : FrameLayout {
|
||||
addView(composeView)
|
||||
|
||||
composeView.layoutParams =
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
||||
|
||||
composeView.setContent {
|
||||
|
||||
@ -23,6 +23,7 @@ import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.license.LicenseScreen
|
||||
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
|
||||
@ -93,6 +94,9 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/widgets/music") {
|
||||
MusicWidgetSettingsScreen()
|
||||
}
|
||||
composable("settings/widgets/clock") {
|
||||
ClockWidgetSettingsScreen()
|
||||
}
|
||||
composable("settings/about") {
|
||||
AboutSettingsScreen()
|
||||
}
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
package de.mm20.launcher2.ui.settings.clockwidget
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.HorizontalPagerIndicator
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockStyle
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
import de.mm20.launcher2.ui.Clock
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
|
||||
@Composable
|
||||
fun ClockWidgetSettingsScreen() {
|
||||
val viewModel: ClockWidgetSettingsScreenVM = viewModel()
|
||||
PreferenceScreen(
|
||||
title = stringResource(R.string.preference_screen_clockwidget)
|
||||
) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
val layout by viewModel.layout.observeAsState()
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_clockwidget_layout),
|
||||
value = layout,
|
||||
items = listOf(
|
||||
stringResource(R.string.preference_clockwidget_layout_vertical) to ClockWidgetLayout.Vertical,
|
||||
stringResource(R.string.preference_clockwidget_layout_horizontal) to ClockWidgetLayout.Horizontal
|
||||
),
|
||||
onValueChanged = {
|
||||
if (it != null) viewModel.setLayout(it)
|
||||
}
|
||||
)
|
||||
val clockStyle by viewModel.clockStyle.observeAsState()
|
||||
ClockStylePreference(
|
||||
layout = layout ?: ClockWidgetLayout.Vertical,
|
||||
value = clockStyle,
|
||||
onValueChanged = {
|
||||
viewModel.setClockStyle(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
fun ClockStylePreference(
|
||||
layout: ClockWidgetLayout,
|
||||
value: ClockStyle?,
|
||||
onValueChanged: (ClockStyle) -> Unit
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_clock_widget_style),
|
||||
summary = stringResource(R.string.preference_clock_widget_style_summary),
|
||||
onClick = {
|
||||
showDialog = true
|
||||
}
|
||||
)
|
||||
if (showDialog && value != null) {
|
||||
val styles = remember {
|
||||
ClockStyle.values().filter { it != ClockStyle.UNRECOGNIZED }
|
||||
}
|
||||
val pagerState = rememberPagerState(styles.indexOf(value))
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDialog = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showDialog = false
|
||||
onValueChanged(styles[pagerState.currentPage])
|
||||
}) {
|
||||
Text(
|
||||
text = stringResource(android.R.string.ok),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDialog = false }) {
|
||||
Text(
|
||||
text = stringResource(android.R.string.cancel),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
text = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
HorizontalPager(
|
||||
count = styles.size,
|
||||
state = pagerState,
|
||||
modifier = Modifier.height(300.dp)
|
||||
) {
|
||||
Clock(style = styles[it], layout = layout, time = System.currentTimeMillis())
|
||||
}
|
||||
HorizontalPagerIndicator(pagerState = pagerState)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package de.mm20.launcher2.ui.settings.clockwidget
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
val layout = dataStore.data.map { it.clockWidget.layout }.asLiveData()
|
||||
fun setLayout(layout: ClockWidgetSettings.ClockWidgetLayout) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setClockWidget(
|
||||
it.clockWidget.toBuilder()
|
||||
.setLayout(layout)
|
||||
).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val clockStyle = dataStore.data.map { it.clockWidget.clockStyle }.asLiveData()
|
||||
fun setClockStyle(clockStyle: ClockWidgetSettings.ClockStyle) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setClockWidget(
|
||||
it.clockWidget.toBuilder()
|
||||
.setClockStyle(clockStyle)
|
||||
).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package de.mm20.launcher2.ui.settings.clockwidget.parts
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import android.text.format.DateFormat
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.em
|
||||
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||
import de.mm20.launcher2.ui.R
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
fun DatePart(
|
||||
time: Long,
|
||||
layout: ClockWidgetLayout
|
||||
) {
|
||||
val verticalLayout = layout == ClockWidgetLayout.Vertical
|
||||
val context = LocalContext.current
|
||||
TextButton(onClick = {
|
||||
val startMillis = System.currentTimeMillis()
|
||||
val builder = CalendarContract.CONTENT_URI.buildUpon()
|
||||
builder.appendPath("time")
|
||||
ContentUris.appendId(builder, startMillis)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
.setData(builder.build())
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
context.startActivity(intent)
|
||||
}) {
|
||||
if (verticalLayout) {
|
||||
Text(
|
||||
text = DateUtils.formatDateTime(
|
||||
context,
|
||||
time,
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR
|
||||
),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White
|
||||
)
|
||||
} else {
|
||||
val line1Format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "EEEE")
|
||||
val line2Format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "MMMM dd yyyy")
|
||||
val format = SimpleDateFormat("$line1Format\n$line2Format")
|
||||
Text(
|
||||
text = format.format(time),
|
||||
lineHeight = 1.2.em,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.settings.widgets
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Audiotrack
|
||||
import androidx.compose.material.icons.rounded.LightMode
|
||||
import androidx.compose.material.icons.rounded.Schedule
|
||||
import androidx.compose.material.icons.rounded.Today
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@ -16,6 +17,13 @@ fun WidgetsSettingsScreen() {
|
||||
val navController = LocalNavController.current
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_widgets)) {
|
||||
item {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_screen_clockwidget),
|
||||
icon = Icons.Rounded.Schedule,
|
||||
onClick = {
|
||||
navController?.navigate("settings/widgets/clock")
|
||||
}
|
||||
)
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_screen_weatherwidget),
|
||||
icon = Icons.Rounded.LightMode,
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
package de.mm20.launcher2.ui.widget.parts
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import de.mm20.launcher2.ui.component.TextClock
|
||||
|
||||
@Composable
|
||||
fun DatePart() {
|
||||
val context = LocalContext.current
|
||||
TextButton(onClick = {
|
||||
val startMillis = System.currentTimeMillis()
|
||||
val builder = CalendarContract.CONTENT_URI.buildUpon()
|
||||
builder.appendPath("time")
|
||||
ContentUris.appendId(builder, startMillis)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
.setData(builder.build())
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
context.startActivity(intent)
|
||||
}) {
|
||||
TextClock(
|
||||
formatFlags = DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -129,16 +129,10 @@
|
||||
android:paddingTop="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Space
|
||||
android:id="@+id/widgetSpacer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.widget.ClockWidget
|
||||
android:id="@+id/clockWidget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.jmedeisis.draglinearlayout.DragLinearLayout
|
||||
android:animateLayoutChanges="true"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user