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:
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user