Add clock widget customizations

This commit is contained in:
MM20 2022-01-08 00:40:49 +01:00
parent eebffa333e
commit ed11330674
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
21 changed files with 584 additions and 304 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
)
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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