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="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="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_category_clock_widget">Uhr</string>
<string name="preference_clock_widget_style">Uhrenstil</string>
<plurals name="calendar_widget_running_events"> <plurals name="calendar_widget_running_events">
<item quantity="one">+%1$d laufender Termin aus vergangenen Tagen</item> <item quantity="one">+%1$d laufender Termin aus vergangenen Tagen</item>
<item quantity="other">+%1$d laufende Termine 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_weatherwidget">Wetter</string>
<string name="preference_screen_musicwidget">Musik</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">Crash-Reporter</string>
<string name="preference_crash_reporter_summary">Fehler- und Absturzberichte</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="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="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_category_clock_widget">Clock</string>
<string name="preference_clock_widget_style">Clock style</string>
<string name="preference_category_grid">Grid</string> <string name="preference_category_grid">Grid</string>
<string name="preference_grid_column_count">Number of columns</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_weatherwidget">Weather</string>
<string name="preference_screen_musicwidget">Music</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">Crash reporter</string>
<string name="preference_crash_reporter_summary">Error and crash reports</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) .setFilterSources(true)
.build() .build()
) )
.setClockWidget(Settings.ClockWidgetSettings
.newBuilder()
.setLayout(Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical)
.setClockStyle(Settings.ClockWidgetSettings.ClockStyle.DigitalClock1)
.build()
)
.build() .build()
} }

View File

@ -37,4 +37,19 @@ message Settings {
} }
MusicWidgetSettings music_widget = 6; 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") alias("accompanist.pager")
.to("com.google.accompanist", "accompanist-pager") .to("com.google.accompanist", "accompanist-pager")
.versionRef("accompanist") .versionRef("accompanist")
alias("accompanist.pagerindicators")
.to("com.google.accompanist", "accompanist-pager-indicators")
.versionRef("accompanist")
alias("accompanist.flowlayout") alias("accompanist.flowlayout")
.to("com.google.accompanist", "accompanist-flowlayout") .to("com.google.accompanist", "accompanist-flowlayout")
.versionRef("accompanist") .versionRef("accompanist")

View File

@ -84,6 +84,7 @@ dependencies {
implementation(libs.accompanist.insets) implementation(libs.accompanist.insets)
implementation(libs.accompanist.systemuicontroller) implementation(libs.accompanist.systemuicontroller)
implementation(libs.accompanist.pager) implementation(libs.accompanist.pager)
implementation(libs.accompanist.pagerindicators)
implementation(libs.accompanist.flowlayout) implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.navigationanimation) 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 package de.mm20.launcher2.ui
import android.content.BroadcastReceiver import androidx.compose.foundation.background
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.provider.AlarmClock
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* 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.material3.LocalContentColor
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.component.DigitalClock import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.widget.parts.DatePart 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 @Composable
fun ClockWidget( fun ClockWidget(
modifier: Modifier = Modifier 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( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
contentAlignment = Alignment.BottomCenter contentAlignment = Alignment.BottomCenter
) { ) {
CompositionLocalProvider(LocalContentColor provides Color.White) { CompositionLocalProvider(
LocalContentColor provides Color.White
) {
if (layout == ClockWidgetLayout.Vertical) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.height(IntrinsicSize.Min) 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( Box(
modifier = Modifier.clickable( modifier = Modifier.clickable(
indication = null, indication = null,
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
) { ) {
context.startActivity(Intent(AlarmClock.ACTION_SHOW_ALARMS).apply { viewModel.launchClockApp(context)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
} }
) { ) {
DigitalClock(time = time) 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)
}
}
}
}
}
} }
} }
@Composable @Composable
fun DynamicZone() { fun Clock(
Column( style: ClockStyle?,
modifier = Modifier.padding(bottom = 16.dp) layout: ClockWidgetLayout,
) { time: Long
DatePart() ) {
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
) {
DatePart(time, layout)
} }
} }

View File

@ -1,6 +1,49 @@
package de.mm20.launcher2.ui.launcher.widgets.clock 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.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) { set(value) {
field = value field = value
if (value) { if (value) {
binding.widgetSpacer.visibility = View.GONE
binding.clockWidget.visibility = View.GONE binding.clockWidget.visibility = View.GONE
binding.searchBar.setRightIcon(R.drawable.ic_done) binding.searchBar.setRightIcon(R.drawable.ic_done)
binding.scrollView.setOnTouchListener(null) binding.scrollView.setOnTouchListener(null)
@ -154,7 +153,6 @@ class LauncherActivity : BaseActivity() {
.start() .start()
} else { } else {
widgetViewModel.saveWidgets(widgets) widgetViewModel.saveWidgets(widgets)
binding.widgetSpacer.visibility = View.VISIBLE
binding.widgetList.layoutTransition = ChangingLayoutTransition() binding.widgetList.layoutTransition = ChangingLayoutTransition()
binding.widgetContainer.layoutTransition = ChangingLayoutTransition() binding.widgetContainer.layoutTransition = ChangingLayoutTransition()
binding.scrollContainer.layoutTransition = ChangingLayoutTransition() binding.scrollContainer.layoutTransition = ChangingLayoutTransition()
@ -226,9 +224,9 @@ class LauncherActivity : BaseActivity() {
binding.searchContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) binding.searchContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
binding.widgetContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) binding.widgetContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val params = binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams val params = binding.clockWidget.layoutParams
params.topMargin = Point().also { windowManager.defaultDisplay.getSize(it) }.y params.height = Point().also { windowManager.defaultDisplay.getSize(it) }.y
binding.widgetSpacer.layoutParams = params binding.clockWidget.layoutParams = params
binding.container.doOnLayout { binding.container.doOnLayout {
adjustWidgetSpace() adjustWidgetSpace()
} }
@ -237,9 +235,7 @@ class LauncherActivity : BaseActivity() {
binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int -> binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
when { when {
/* Hide searchbar*/ /* Hide searchbar*/
scrollY > oldScrollY && ((searchVisibility && scrollY > binding.searchBar.height) || widgetEditMode || scrollY > oldScrollY && ((scrollY > binding.searchBar.height) || widgetEditMode) -> {
scrollY > binding.widgetSpacer.height + binding.searchBar.height
+ (binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams).topMargin) -> {
var newTransY = binding.searchBar.translationY - scrollY + oldScrollY var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
if (newTransY < -binding.searchBar.height.toFloat() * 1.5f) { if (newTransY < -binding.searchBar.height.toFloat() * 1.5f) {
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 binding.searchBar.translationY = newTransY
} }
} }
if (scrollY > 0 && (searchVisibility || widgetEditMode || if (scrollY > 0 && (searchVisibility || widgetEditMode)
scrollY > binding.widgetSpacer.height
+ (binding.widgetSpacer.layoutParams as LinearLayout.LayoutParams).topMargin)
) { ) {
binding.searchBar.raise() binding.searchBar.raise()
} else binding.searchBar.drop() } else binding.searchBar.drop()
@ -455,13 +449,10 @@ class LauncherActivity : BaseActivity() {
} }
private fun adjustWidgetSpace() { private fun adjustWidgetSpace() {
val firstWidget = binding.clockWidget val height = binding.scrollView.height - binding.searchBar.height - 8 * dp
val m = binding.scrollContainer.paddingTop + binding.clockWidget.layoutParams = binding.clockWidget.layoutParams.also {
(firstWidget.layoutParams as LinearLayout.LayoutParams).run { topMargin + bottomMargin } it.height = height.toInt()
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
} }

View File

@ -30,7 +30,7 @@ class ClockWidget : FrameLayout {
addView(composeView) addView(composeView)
composeView.layoutParams = composeView.layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
composeView.setContent { 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.locals.LocalNavController
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen 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.debug.DebugSettingsScreen
import de.mm20.launcher2.ui.settings.license.LicenseScreen import de.mm20.launcher2.ui.settings.license.LicenseScreen
import de.mm20.launcher2.ui.settings.main.MainSettingsScreen import de.mm20.launcher2.ui.settings.main.MainSettingsScreen
@ -93,6 +94,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/widgets/music") { composable("settings/widgets/music") {
MusicWidgetSettingsScreen() MusicWidgetSettingsScreen()
} }
composable("settings/widgets/clock") {
ClockWidgetSettingsScreen()
}
composable("settings/about") { composable("settings/about") {
AboutSettingsScreen() 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.Icons
import androidx.compose.material.icons.rounded.Audiotrack import androidx.compose.material.icons.rounded.Audiotrack
import androidx.compose.material.icons.rounded.LightMode import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.Schedule
import androidx.compose.material.icons.rounded.Today import androidx.compose.material.icons.rounded.Today
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -16,6 +17,13 @@ fun WidgetsSettingsScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
PreferenceScreen(title = stringResource(R.string.preference_screen_widgets)) { PreferenceScreen(title = stringResource(R.string.preference_screen_widgets)) {
item { item {
Preference(
title = stringResource(R.string.preference_screen_clockwidget),
icon = Icons.Rounded.Schedule,
onClick = {
navController?.navigate("settings/widgets/clock")
}
)
Preference( Preference(
title = stringResource(R.string.preference_screen_weatherwidget), title = stringResource(R.string.preference_screen_weatherwidget),
icon = Icons.Rounded.LightMode, 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:paddingTop="8dp"
android:orientation="vertical"> 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 <de.mm20.launcher2.ui.legacy.widget.ClockWidget
android:id="@+id/clockWidget" android:id="@+id/clockWidget"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent" />
android:layout_marginTop="8dp" />
<com.jmedeisis.draglinearlayout.DragLinearLayout <com.jmedeisis.draglinearlayout.DragLinearLayout
android:animateLayoutChanges="true" android:animateLayoutChanges="true"