diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/MainActivity.kt b/app/src/main/java/de/jeanlucmakiola/agendula/MainActivity.kt
index 1ac4cd6..f6386e4 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/MainActivity.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/MainActivity.kt
@@ -28,7 +28,6 @@ import de.jeanlucmakiola.agendula.data.di.LaunchHook
import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.agendula.ui.imports.importIntentUri
import de.jeanlucmakiola.agendula.ui.navigation.AppShortcuts
-import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
import de.jeanlucmakiola.agendula.ui.RootScreen
import de.jeanlucmakiola.agendula.ui.crash.CrashReportActivity
import de.jeanlucmakiola.agendula.ui.navigation.NavRequest
@@ -88,15 +87,15 @@ class MainActivity : ComponentActivity() {
setContent {
val settingsViewModel: SettingsViewModel = hiltViewModel()
val ui by settingsViewModel.state.collectAsStateWithLifecycle()
- val darkTheme = when (ui.settings.themeMode) {
- ThemeMode.SYSTEM -> isSystemInDarkTheme()
- ThemeMode.LIGHT -> false
- ThemeMode.DARK -> true
- }
+ val darkTheme = ui.settings.themeMode.isDark(isSystemInDarkTheme())
val context = LocalContext.current
val use24Hour = ui.settings.timeFormat.is24Hour(DateFormat.is24HourFormat(context))
val firstDayOfWeek = ui.settings.weekStart ?: localeFirstDayOfWeek()
- AgendulaTheme(darkTheme = darkTheme, dynamicColor = ui.settings.dynamicColor) {
+ AgendulaTheme(
+ darkTheme = darkTheme,
+ dynamicColor = ui.settings.dynamicColor,
+ pureBlack = ui.settings.themeMode.isPureBlack,
+ ) {
CompositionLocalProvider(
LocalUse24HourFormat provides use24Hour,
LocalFirstDayOfWeek provides firstDayOfWeek,
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/agendula/data/prefs/SettingsPrefs.kt
index 15d6244..73f09c2 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/data/prefs/SettingsPrefs.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/data/prefs/SettingsPrefs.kt
@@ -31,7 +31,18 @@ import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
-enum class ThemeMode { SYSTEM, LIGHT, DARK }
+/** [BLACK] is dark with pure-black surfaces, for AMOLED screens. */
+enum class ThemeMode {
+ SYSTEM, LIGHT, DARK, BLACK;
+
+ fun isDark(systemDark: Boolean): Boolean = when (this) {
+ SYSTEM -> systemDark
+ LIGHT -> false
+ DARK, BLACK -> true
+ }
+
+ val isPureBlack: Boolean get() = this == BLACK
+}
/** Clock convention: AUTO follows the device's 24-hour switch. */
enum class TimeFormatPref { AUTO, TWELVE_HOUR, TWENTY_FOUR_HOUR }
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/data/reminders/TaskNotifier.kt b/app/src/main/java/de/jeanlucmakiola/agendula/data/reminders/TaskNotifier.kt
index d7f087d..4adc2c5 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/data/reminders/TaskNotifier.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/data/reminders/TaskNotifier.kt
@@ -18,6 +18,7 @@ import androidx.core.content.edit
import de.jeanlucmakiola.agendula.MainActivity
import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.prefs.Settings
+import de.jeanlucmakiola.agendula.domain.readableDescription
import de.jeanlucmakiola.agendula.data.prefs.is24Hour
import de.jeanlucmakiola.agendula.data.di.ApplicationScope
import de.jeanlucmakiola.agendula.data.tasks.ProviderResolver
@@ -84,6 +85,7 @@ class TaskNotifier @Inject constructor(
),
)
}
+ val description = task.description?.readableDescription()?.trim()?.takeIf { it.isNotBlank() }
val tapIntent = PendingIntent.getActivity(
context,
task.taskId.toInt(),
@@ -93,7 +95,10 @@ class TaskNotifier @Inject constructor(
val notification = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
- .apply { if (text != null) setContentText(text) }
+ .apply { (text ?: description)?.let(::setContentText) }
+ .apply {
+ expandedText(text, description)?.let { setStyle(NotificationCompat.BigTextStyle().bigText(it)) }
+ }
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
@@ -240,3 +245,10 @@ private fun relativeDay(date: LocalDate, today: LocalDate, words: RelativeDays,
else -> null
}
}
+
+/** The expanded body of a due reminder: the due line, then the task's description. */
+internal fun expandedText(dueLine: String?, description: String?): String? = when {
+ description == null -> null
+ dueLine == null -> description
+ else -> "$dueLine\n\n$description"
+}
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/settings/AppearanceScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/settings/AppearanceScreen.kt
index 0dbbc9f..e08484b 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/settings/AppearanceScreen.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/settings/AppearanceScreen.kt
@@ -165,6 +165,7 @@ internal fun AppearanceScreen(
options = ThemeMode.entries,
selected = state.settings.themeMode,
label = { stringResource(themeLabel(it)) },
+ summary = { if (it == ThemeMode.BLACK) stringResource(R.string.settings_theme_black_summary) else null },
onSelect = viewModel::setThemeMode,
onDismiss = { showTheme = false },
)
@@ -200,4 +201,5 @@ private fun themeLabel(mode: ThemeMode): Int = when (mode) {
ThemeMode.SYSTEM -> R.string.settings_theme_system
ThemeMode.LIGHT -> R.string.settings_theme_light
ThemeMode.DARK -> R.string.settings_theme_dark
+ ThemeMode.BLACK -> R.string.settings_theme_black
}
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/theme/Theme.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/theme/Theme.kt
index 2bca6f8..6e02f5f 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/theme/Theme.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/theme/Theme.kt
@@ -15,6 +15,7 @@ import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme
fun AgendulaTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
+ pureBlack: Boolean = false,
content: @Composable () -> Unit,
) {
FloretExpressiveTheme(
@@ -22,6 +23,7 @@ fun AgendulaTheme(
darkScheme = AgendulaDarkFallback,
darkTheme = darkTheme,
dynamicColor = dynamicColor,
+ pureBlack = pureBlack,
typography = AgendulaTypography,
content = content,
)
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/widget/TaskWidgetConfigActivity.kt b/app/src/main/java/de/jeanlucmakiola/agendula/widget/TaskWidgetConfigActivity.kt
index 7287add..504720c 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/widget/TaskWidgetConfigActivity.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/widget/TaskWidgetConfigActivity.kt
@@ -39,7 +39,6 @@ import dagger.hilt.android.AndroidEntryPoint
import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.prefs.Settings
import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
-import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.agendula.domain.SavedFilter
import de.jeanlucmakiola.agendula.domain.SmartList
@@ -135,12 +134,12 @@ class TaskWidgetConfigActivity : ComponentActivity() {
rowColor = parseWidgetRowColor(state[WIDGET_ROW_COLOR_KEY])
selectedListIds = parseWidgetListIds(state[WIDGET_LIST_IDS_KEY])
}
- val dark = when (settings.themeMode) {
- ThemeMode.SYSTEM -> isSystemInDarkTheme()
- ThemeMode.LIGHT -> false
- ThemeMode.DARK -> true
- }
- AgendulaTheme(darkTheme = dark, dynamicColor = settings.dynamicColor) {
+ val dark = settings.themeMode.isDark(isSystemInDarkTheme())
+ AgendulaTheme(
+ darkTheme = dark,
+ dynamicColor = settings.dynamicColor,
+ pureBlack = settings.themeMode.isPureBlack,
+ ) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {}
WidgetFilterPicker(
kind = kind,
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/widget/WidgetData.kt b/app/src/main/java/de/jeanlucmakiola/agendula/widget/WidgetData.kt
index e1c0c34..c5e626c 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/widget/WidgetData.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/widget/WidgetData.kt
@@ -82,7 +82,7 @@ internal fun effectiveThemeMode(appThemeMode: ThemeMode, override: WidgetThemeMo
override ?: when (appThemeMode) {
ThemeMode.SYSTEM -> WidgetThemeMode.SYSTEM
ThemeMode.LIGHT -> WidgetThemeMode.LIGHT
- ThemeMode.DARK -> WidgetThemeMode.DARK
+ ThemeMode.DARK, ThemeMode.BLACK -> WidgetThemeMode.DARK
}
/** The smart lists a widget can show, in picker order. */
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c7f4913..d623542 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -449,6 +449,8 @@
Follow system
Light
Dark
+ Black
+ Dark with pure black backgrounds, for AMOLED screens
App name
Show Agendula as “Tasks” in your launcher. Only the launcher name changes; the icon may move to a new spot. Agendula closes when you switch, so reopen it from the launcher.
Dynamic colour
diff --git a/app/src/test/java/de/jeanlucmakiola/agendula/data/reminders/ExpandedTextTest.kt b/app/src/test/java/de/jeanlucmakiola/agendula/data/reminders/ExpandedTextTest.kt
new file mode 100644
index 0000000..4ca7d65
--- /dev/null
+++ b/app/src/test/java/de/jeanlucmakiola/agendula/data/reminders/ExpandedTextTest.kt
@@ -0,0 +1,22 @@
+package de.jeanlucmakiola.agendula.data.reminders
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.jupiter.api.Test
+
+class ExpandedTextTest {
+
+ @Test
+ fun `no description means no expanded body`() {
+ assertThat(expandedText("Due today", null)).isNull()
+ }
+
+ @Test
+ fun `description alone when there is no due line`() {
+ assertThat(expandedText(null, "Buy oat milk")).isEqualTo("Buy oat milk")
+ }
+
+ @Test
+ fun `due line comes first, separated by a blank line`() {
+ assertThat(expandedText("Due today", "Buy oat milk")).isEqualTo("Due today\n\nBuy oat milk")
+ }
+}
diff --git a/app/src/test/java/de/jeanlucmakiola/agendula/widget/WidgetDataTest.kt b/app/src/test/java/de/jeanlucmakiola/agendula/widget/WidgetDataTest.kt
index 1951ca0..a054a77 100644
--- a/app/src/test/java/de/jeanlucmakiola/agendula/widget/WidgetDataTest.kt
+++ b/app/src/test/java/de/jeanlucmakiola/agendula/widget/WidgetDataTest.kt
@@ -210,6 +210,7 @@ class WidgetDataTest {
fun `no override resolves to whatever the app's own theme means`() {
assertThat(readyWithTheme(ThemeMode.DARK).effectiveThemeMode(null)).isEqualTo(WidgetThemeMode.DARK)
assertThat(readyWithTheme(ThemeMode.SYSTEM).effectiveThemeMode(null)).isEqualTo(WidgetThemeMode.SYSTEM)
+ assertThat(readyWithTheme(ThemeMode.BLACK).effectiveThemeMode(null)).isEqualTo(WidgetThemeMode.DARK)
}
@Test