diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index 1769c4c4..e197dc7d 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -404,7 +404,6 @@ Lizenziert unter der GNU General Public License 3.0 Diese Funktion ist in dieser Version von %1$s nicht verfügbar. Uhr - Uhrenstil +%1$d laufender Termin aus vergangenen Tagen +%1$d laufende Termine aus vergangenen Tagen @@ -432,6 +431,13 @@ Wetter Musik + Uhr + Layout + Vertikal + Horizontal + Stil + Uhr auswählen + Crash-Reporter Fehler- und Absturzberichte diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 4a28e84b..2f2d3c9e 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -451,7 +451,6 @@ Licensed under the GNU General Public License 3.0 This feature is not available in this version of %1$s Clock - Clock style Grid Number of columns @@ -470,6 +469,13 @@ Weather Music + Clock + Layout + Vertical + Horizontal + Style + Select a clock + Crash reporter Error and crash reports diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt index 2b09b9ca..905bfe16 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt @@ -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() } \ No newline at end of file diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index 169f294f..b503cf14 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -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; + } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 99087197..8701ead3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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") diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 38cc9d1b..e9dfaf3e 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -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) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/Clocks.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/Clocks.kt deleted file mode 100644 index bb539ed5..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/Clocks.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index 1904c3bd..c40ef0d7 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -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) } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt index 187e0efd..413c759f 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidgetVM.kt @@ -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 = 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 + }) + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt new file mode 100644 index 00000000..2ae7c767 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt @@ -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 + ) + ) + ) + } + } + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt new file mode 100644 index 00000000..1437045d --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt @@ -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, + ) + ) +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt new file mode 100644 index 00000000..da736928 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt @@ -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 + ) +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt index 6f00569a..2186931c 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt @@ -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() + } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/ClockWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/ClockWidget.kt index d378801f..36b1896b 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/ClockWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/ClockWidget.kt @@ -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 { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index e23ad411..0ac04017 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -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() } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreen.kt new file mode 100644 index 00000000..a755d97c --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreen.kt @@ -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) + } + } + ) + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt new file mode 100644 index 00000000..304d143a --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt @@ -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() + } + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/parts/DatePart.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/parts/DatePart.kt new file mode 100644 index 00000000..75c5bf03 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/parts/DatePart.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/widgets/WidgetsSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/widgets/WidgetsSettingsScreen.kt index bcc42009..22d4df47 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/widgets/WidgetsSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/widgets/WidgetsSettingsScreen.kt @@ -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, diff --git a/ui/src/main/java/de/mm20/launcher2/ui/widget/parts/DatePart.kt b/ui/src/main/java/de/mm20/launcher2/ui/widget/parts/DatePart.kt deleted file mode 100644 index 86cd04cf..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/widget/parts/DatePart.kt +++ /dev/null @@ -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 - ) - } -} \ No newline at end of file diff --git a/ui/src/main/res/layout/activity_launcher.xml b/ui/src/main/res/layout/activity_launcher.xml index 612cfe85..d624488c 100644 --- a/ui/src/main/res/layout/activity_launcher.xml +++ b/ui/src/main/res/layout/activity_launcher.xml @@ -129,16 +129,10 @@ android:paddingTop="8dp" android:orientation="vertical"> - - + android:layout_height="match_parent" />