Clock dynamic zone: alarm shows trigger time, static rankings, wider dot gap

- Alarm part now shows the time the alarm goes off instead of a relative
  "in X hours" span, formatted per the device's 12/24h setting.
- Reorder the dynamic zone: alarm (100) always sits below the clock, date
  (90) follows it.
- Calendar part ranking is now static (30) instead of boosting events that
  start within 30 minutes; drop the now-unused time plumbing.
- Widen the agenda dot column from 12dp to 20dp for more dot/time spacing.
This commit is contained in:
2026-09-11 13:13:35 +02:00
parent 05c7f0f60d
commit a0f9413d40
3 changed files with 25 additions and 38 deletions

View File

@@ -5,13 +5,13 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.icu.text.DateFormat
import android.icu.util.ULocale
import android.provider.AlarmClock
import android.text.format.DateUtils
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
@@ -21,6 +21,9 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.locals.LocalTimeFormat
import de.mm20.launcher2.ui.utils.isTwentyFourHours
import java.util.Date
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.*
@@ -51,7 +54,8 @@ class AlarmPartProvider : PartProvider {
if (alarm > it + AlarmWindow) {
send(0)
} else {
send(60)
// Highest ranking, so the alarm always sits directly below the clock.
send(100)
}
}
}
@@ -77,11 +81,15 @@ class AlarmPartProvider : PartProvider {
@Composable
override fun Component(compactLayout: Boolean) {
val context = LocalContext.current
val timeFormat = LocalTimeFormat.current
val alarmTime by nextAlarmTime
val time by this.time.collectAsState(System.currentTimeMillis())
alarmTime?.let {
alarmTime?.let { alarm ->
val skeleton = if (timeFormat.isTwentyFourHours(context)) "HH:mm" else "hh:mm a"
val alarmText = DateFormat.getInstanceForSkeleton(skeleton, ULocale.getDefault())
.format(Date(alarm))
if (!compactLayout) {
@@ -99,12 +107,7 @@ class AlarmPartProvider : PartProvider {
)
Text(
modifier = Modifier.padding(start = 12.dp),
text = DateUtils.getRelativeTimeSpanString(
it,
time,
DateUtils.MINUTE_IN_MILLIS
)
.toString(),
text = alarmText,
)
}
} else {
@@ -123,12 +126,7 @@ class AlarmPartProvider : PartProvider {
)
Text(
modifier = Modifier.padding(start = 12.dp),
text = DateUtils.getRelativeTimeSpanString(
it,
time,
DateUtils.MINUTE_IN_MILLIS
)
.toString(),
text = alarmText,
style = MaterialTheme.typography.titleMedium
)
}

View File

@@ -54,6 +54,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -69,17 +70,14 @@ import java.util.Date
*/
private const val MaxRows = 4
/** An event that starts within this time frame (or is running) raises the ranking. */
private const val SoonThreshold = 30 * 60 * 1000L
/** Re-query interval, so the agenda survives calendar changes that don't notify. */
private const val RefreshInterval = 15 * 60 * 1000L
private const val RankingToday = 30
private const val RankingSoon = 70
/** Static ranking: the agenda is shown whenever there are events left today. */
private const val Ranking = 30
/** Width of the dot column. Kept fixed so the rows line up with each other. */
private val DotColumn = 12.dp
private val DotColumn = 20.dp
/** Width of the time column. Wide enough for "11:59 PM" and "all-day". */
private val TimeColumn = 64.dp
@@ -100,17 +98,12 @@ class CalendarPartProvider : PartProvider, KoinComponent {
private val searchSettings: CalendarSearchSettings by inject()
private val agenda = MutableStateFlow<List<CalendarEvent>>(emptyList())
private val time = MutableStateFlow(System.currentTimeMillis())
override fun setTime(time: Long) {
this.time.value = time
}
override fun getRanking(context: Context): Flow<Int> = channelFlow {
// Load the agenda in the background; the ranking itself only depends on the
// already known agenda and the current time, so it can be emitted right away.
// already known agenda, so it can be emitted right away.
launch { observeAgenda(context) }
combine(agenda, time) { events, now -> ranking(events, now) }
agenda.map { ranking(it) }
.distinctUntilChanged()
.collect { send(it) }
}
@@ -167,13 +160,8 @@ class CalendarPartProvider : PartProvider, KoinComponent {
}
}
private fun ranking(events: List<CalendarEvent>, now: Long): Int {
if (events.isEmpty()) return 0
val soon = events.any { event ->
val start = event.startTime
!event.allDay && start != null && start <= now + SoonThreshold && event.endTime > now
}
return if (soon) RankingSoon else RankingToday
private fun ranking(events: List<CalendarEvent>): Int {
return if (events.isEmpty()) 0 else Ranking
}
private fun endOfDay(now: Long): Long {

View File

@@ -24,7 +24,8 @@ import java.util.*
class DatePartProvider : PartProvider {
override fun getRanking(context: Context): Flow<Int> = flow {
emit(1)
// Second highest, so the date follows the alarm below the clock.
emit(90)
}
@Composable