4 Commits

Author SHA1 Message Date
6aba977823 Bump version to 1.40.2-typing.6
Some checks failed
Trigger F-Droid repository rebuild / trigger (release) Has been cancelled
Alarm shows its trigger time and matches the date part's typography, static
dynamic-zone rankings (alarm 100, date 90, calendar 30), and a wider agenda
dot column.
2026-09-11 13:23:13 +02:00
229e5d1d32 Widen the agenda dot column to 24dp for more dot/time spacing 2026-09-11 13:22:59 +02:00
cb07acb577 Match the alarm part's typography to the date part
The alarm text used the button's default labelLarge in the vertical layout
and titleMedium in the compact one. Use titleMedium / titleLarge(Medium),
the same styles the date part uses, so both parts look alike.
2026-09-11 13:22:59 +02:00
a0f9413d40 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.
2026-09-11 13:13:35 +02:00
4 changed files with 31 additions and 40 deletions

View File

@@ -34,8 +34,8 @@ android {
applicationId = "de.mm20.launcher2"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = System.getenv("VERSION_CODE_OVERRIDE")?.toIntOrNull() ?: 2026091004
versionName = "1.40.2-typing.5"
versionCode = System.getenv("VERSION_CODE_OVERRIDE")?.toIntOrNull() ?: 2026091005
versionName = "1.40.2-typing.6"
signingConfig = signingConfigs.getByName("debug")
}

View File

@@ -5,22 +5,26 @@ 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
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
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 +55,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 +82,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 +108,8 @@ 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
)
}
} else {
@@ -123,13 +128,10 @@ class AlarmPartProvider : PartProvider {
)
Text(
modifier = Modifier.padding(start = 12.dp),
text = DateUtils.getRelativeTimeSpanString(
it,
time,
DateUtils.MINUTE_IN_MILLIS
text = alarmText,
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Medium
)
.toString(),
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 = 24.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