Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b018b6a92b |
No files matched your search
@@ -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,
|
||||
@@ -105,7 +104,6 @@ class MainActivity : ComponentActivity() {
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navRequest = navRequest,
|
||||
onNavRequestConsumed = { navRequest = null },
|
||||
onReturnHome = ::finish,
|
||||
)
|
||||
pendingCrashReport?.let { report ->
|
||||
CrashReportDialog(
|
||||
@@ -154,7 +152,6 @@ class MainActivity : ComponentActivity() {
|
||||
private const val EXTRA_FILTER_ID = "de.jeanlucmakiola.agendula.extra.FILTER_ID"
|
||||
private const val EXTRA_SMART_LIST = "de.jeanlucmakiola.agendula.extra.SMART_LIST"
|
||||
private const val EXTRA_LIST_ID = "de.jeanlucmakiola.agendula.extra.LIST_ID"
|
||||
private const val EXTRA_RETURN_HOME = "de.jeanlucmakiola.agendula.extra.RETURN_HOME"
|
||||
private const val EXTRA_PRESET_LIST_ID = "de.jeanlucmakiola.agendula.extra.PRESET_LIST_ID"
|
||||
private const val SHARED_TITLE_LIMIT = 500
|
||||
private const val NO_OCCURRENCE = -1L
|
||||
@@ -163,16 +160,10 @@ class MainActivity : ComponentActivity() {
|
||||
* Opens a task's detail (reminder taps). [occurrenceStart] (epoch millis)
|
||||
* picks the occurrence of a recurring task.
|
||||
*/
|
||||
fun taskIntent(
|
||||
context: Context,
|
||||
taskId: Long,
|
||||
occurrenceStart: Long? = null,
|
||||
returnHome: Boolean = false,
|
||||
): Intent =
|
||||
fun taskIntent(context: Context, taskId: Long, occurrenceStart: Long? = null): Intent =
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
putExtra(EXTRA_TASK_ID, taskId)
|
||||
putExtra(EXTRA_OCCURRENCE_START, occurrenceStart ?: NO_OCCURRENCE)
|
||||
putExtra(EXTRA_RETURN_HOME, returnHome)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
|
||||
@@ -224,7 +215,6 @@ class MainActivity : ComponentActivity() {
|
||||
when (intent.action) {
|
||||
ACTION_NEW_TASK -> return NavRequest.NewTask(
|
||||
listId = intent.getLongExtra(EXTRA_PRESET_LIST_ID, -1L).takeIf { it > 0L },
|
||||
returnHome = true,
|
||||
)
|
||||
ACTION_TODAY -> return NavRequest.OpenSmart(SmartList.TODAY)
|
||||
ACTION_OPEN_SMART -> intent.getStringExtra(EXTRA_SMART_LIST)
|
||||
@@ -239,12 +229,12 @@ class MainActivity : ComponentActivity() {
|
||||
if (intent.action == Intent.ACTION_SEND && intent.type?.startsWith("text/plain") == true) {
|
||||
val shared = intent.getStringExtra(Intent.EXTRA_SUBJECT)?.takeIf { it.isNotBlank() }
|
||||
?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
return NavRequest.NewTask(shared?.trim()?.take(SHARED_TITLE_LIMIT), returnHome = true)
|
||||
return NavRequest.NewTask(shared?.trim()?.take(SHARED_TITLE_LIMIT))
|
||||
}
|
||||
val taskId = intent.getLongExtra(EXTRA_TASK_ID, -1L).takeIf { it > 0L } ?: return null
|
||||
val occurrence = intent.getLongExtra(EXTRA_OCCURRENCE_START, NO_OCCURRENCE)
|
||||
.takeIf { it != NO_OCCURRENCE }
|
||||
return NavRequest.OpenTask(taskId, occurrence, intent.getBooleanExtra(EXTRA_RETURN_HOME, false))
|
||||
return NavRequest.OpenTask(taskId, occurrence)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -46,7 +46,6 @@ fun RootScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
navRequest: NavRequest? = null,
|
||||
onNavRequestConsumed: () -> Unit = {},
|
||||
onReturnHome: () -> Unit = {},
|
||||
permissionViewModel: PermissionViewModel = hiltViewModel(),
|
||||
) {
|
||||
val permission by permissionViewModel.state.collectAsStateWithLifecycle()
|
||||
@@ -87,7 +86,6 @@ fun RootScreen(
|
||||
modifier = modifier,
|
||||
navRequest = navRequest,
|
||||
onNavRequestConsumed = onNavRequestConsumed,
|
||||
onReturnHome = onReturnHome,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,7 +101,6 @@ private fun ReadyGate(
|
||||
modifier: Modifier = Modifier,
|
||||
navRequest: NavRequest? = null,
|
||||
onNavRequestConsumed: () -> Unit = {},
|
||||
onReturnHome: () -> Unit = {},
|
||||
onboardingViewModel: OnboardingViewModel = hiltViewModel(),
|
||||
) {
|
||||
val done by onboardingViewModel.done.collectAsStateWithLifecycle()
|
||||
@@ -116,7 +113,6 @@ private fun ReadyGate(
|
||||
modifier = modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface),
|
||||
navRequest = navRequest,
|
||||
onNavRequestConsumed = onNavRequestConsumed,
|
||||
onReturnHome = onReturnHome,
|
||||
)
|
||||
false -> ReadableWidth(modifier) {
|
||||
OnboardingFlow(modifier = Modifier.fillMaxSize(), viewModel = onboardingViewModel)
|
||||
|
||||
@@ -7,11 +7,6 @@ import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -51,19 +46,8 @@ fun AgendulaNavHost(
|
||||
modifier: Modifier = Modifier,
|
||||
navRequest: NavRequest? = null,
|
||||
onNavRequestConsumed: () -> Unit = {},
|
||||
onReturnHome: () -> Unit = {},
|
||||
) {
|
||||
val nav = rememberNavController()
|
||||
// The entry a widget, tile, shortcut or share opened; closing it leaves the app (#93).
|
||||
var returnHomeEntry by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val backStack by nav.currentBackStack.collectAsState()
|
||||
LaunchedEffect(backStack, returnHomeEntry) {
|
||||
val id = returnHomeEntry ?: return@LaunchedEffect
|
||||
if (backStack.none { it.id == id }) {
|
||||
returnHomeEntry = null
|
||||
onReturnHome()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(navRequest) {
|
||||
when (navRequest) {
|
||||
@@ -87,9 +71,6 @@ fun AgendulaNavHost(
|
||||
}
|
||||
is NavRequest.Import -> nav.navigate(Dest.Import.build(navRequest.uri))
|
||||
}
|
||||
val returnHome = (navRequest as? NavRequest.NewTask)?.returnHome == true ||
|
||||
(navRequest as? NavRequest.OpenTask)?.returnHome == true
|
||||
returnHomeEntry = if (returnHome) nav.currentBackStackEntry?.id else null
|
||||
onNavRequestConsumed()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,8 @@ import de.jeanlucmakiola.agendula.domain.SmartList
|
||||
* [AgendulaNavHost], which pushes it over whatever is showing.
|
||||
*/
|
||||
sealed interface NavRequest {
|
||||
/**
|
||||
* One task's detail; [occurrenceStart] (epoch millis) picks the occurrence of a series.
|
||||
* [returnHome] leaves the app once the detail is closed (a widget row, #93).
|
||||
*/
|
||||
data class OpenTask(
|
||||
val taskId: Long,
|
||||
val occurrenceStart: Long? = null,
|
||||
val returnHome: Boolean = false,
|
||||
) : NavRequest
|
||||
/** One task's detail; [occurrenceStart] (epoch millis) picks the occurrence of a series. */
|
||||
data class OpenTask(val taskId: Long, val occurrenceStart: Long? = null) : NavRequest
|
||||
|
||||
/** Settings → Accounts, where a sync report's detail lives. */
|
||||
data object OpenAccounts : NavRequest
|
||||
@@ -28,13 +21,8 @@ sealed interface NavRequest {
|
||||
/**
|
||||
* A new task, optionally titled with text shared from another app, or
|
||||
* preset to a list — a widget's own add button, when it shows one list.
|
||||
* [returnHome] leaves the app once the form is closed (#93).
|
||||
*/
|
||||
data class NewTask(
|
||||
val title: String? = null,
|
||||
val listId: Long? = null,
|
||||
val returnHome: Boolean = false,
|
||||
) : NavRequest
|
||||
data class NewTask(val title: String? = null, val listId: Long? = null) : NavRequest
|
||||
|
||||
/** One of the smart lists — the launcher's "Today" shortcut, a widget header. */
|
||||
data class OpenSmart(val list: SmartList) : NavRequest
|
||||
|
||||
@@ -140,6 +140,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 },
|
||||
)
|
||||
@@ -167,4 +168,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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -328,7 +328,7 @@ internal fun AgendaTaskRow(
|
||||
val context = LocalContext.current
|
||||
val title = row.title.ifBlank { context.getString(R.string.task_untitled) }
|
||||
val open = actionStartActivity(
|
||||
MainActivity.taskIntent(context, row.taskId, row.occurrenceStart?.toEpochMilliseconds(), returnHome = true),
|
||||
MainActivity.taskIntent(context, row.taskId, row.occurrenceStart?.toEpochMilliseconds()),
|
||||
)
|
||||
val onColor = if (colored) GlanceColorProvider(Color.White) else GlanceTheme.colors.onSurface
|
||||
val onColorVariant = if (colored) GlanceColorProvider(Color.White.copy(alpha = 0.8f)) else GlanceTheme.colors.onSurfaceVariant
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -339,7 +339,7 @@ private fun ListDot(color: Int) {
|
||||
}
|
||||
|
||||
private fun openTask(context: Context, row: WidgetTaskRow): Action =
|
||||
actionStartActivity(MainActivity.taskIntent(context, row.taskId, row.occurrenceStart?.toEpochMilliseconds(), returnHome = true))
|
||||
actionStartActivity(MainActivity.taskIntent(context, row.taskId, row.occurrenceStart?.toEpochMilliseconds()))
|
||||
|
||||
private fun completeTask(row: WidgetTaskRow): Action =
|
||||
actionRunCallback<CompleteTaskAction>(
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -448,6 +448,8 @@
|
||||
<string name="settings_theme_system">Follow system</string>
|
||||
<string name="settings_theme_light">Light</string>
|
||||
<string name="settings_theme_dark">Dark</string>
|
||||
<string name="settings_theme_black">Black</string>
|
||||
<string name="settings_theme_black_summary">Dark with pure black backgrounds, for AMOLED screens</string>
|
||||
<string name="settings_dynamic_color">Dynamic colour</string>
|
||||
<string name="settings_dynamic_color_hint">Use colours from your wallpaper</string>
|
||||
<string name="settings_section_reminders">Notifications</string>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user