Completes v2.7 Branch 2. Wires the import core into the app: - Manifest ACTION_VIEW/SEND for text/calendar; MainActivity parses the incoming Uri (content/file only, so calendula:// deep-links don't match) and routes it through RootScreen → CalendarHost like the other one-shot intents. - ImportViewModel reads + parses the file and routes by count: one event → the prefilled create form for review (EventEditViewModel.openImported, which freezes the reminder default so the file's reminders win); many → ImportScreen with a writable-calendar picker, then a bulk import (UID dedup) and a result summary. - ImportScreen also surfaces parser warnings (skipped recurrence overrides, ignored attendees, unknown-timezone fallback). Strings EN+DE. Package is ui.imports (not ui.import — Java keyword). lint + test + assembleDebug green. No v2.7 tag until on-device review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
3.2 KiB
Kotlin
83 lines
3.2 KiB
Kotlin
package de.jeanlucmakiola.calendula.ui
|
|
|
|
import android.Manifest
|
|
import android.content.pm.PackageManager
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.DisposableEffect
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.hilt.navigation.compose.hiltViewModel
|
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.lifecycle.Lifecycle
|
|
import androidx.lifecycle.LifecycleEventObserver
|
|
import de.jeanlucmakiola.calendula.ui.permission.PermissionScreen
|
|
import de.jeanlucmakiola.calendula.ui.permission.ReminderOnboardingScreen
|
|
import de.jeanlucmakiola.calendula.ui.permission.ReminderOnboardingViewModel
|
|
|
|
@Composable
|
|
fun RootScreen(
|
|
modifier: Modifier = Modifier,
|
|
requestedDetailKey: LongArray? = null,
|
|
onDetailKeyConsumed: () -> Unit = {},
|
|
widgetNavRequest: WidgetNavRequest? = null,
|
|
onWidgetNavConsumed: () -> Unit = {},
|
|
requestedImportUri: android.net.Uri? = null,
|
|
onImportConsumed: () -> Unit = {},
|
|
) {
|
|
val context = LocalContext.current
|
|
var hasPermission by remember {
|
|
mutableStateOf(
|
|
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR)
|
|
== PackageManager.PERMISSION_GRANTED
|
|
)
|
|
}
|
|
|
|
val lifecycle = LocalLifecycleOwner.current.lifecycle
|
|
DisposableEffect(lifecycle) {
|
|
val obs = LifecycleEventObserver { _, event ->
|
|
if (event == Lifecycle.Event.ON_RESUME) {
|
|
hasPermission = ContextCompat.checkSelfPermission(
|
|
context, Manifest.permission.READ_CALENDAR
|
|
) == PackageManager.PERMISSION_GRANTED
|
|
}
|
|
}
|
|
lifecycle.addObserver(obs)
|
|
onDispose { lifecycle.removeObserver(obs) }
|
|
}
|
|
|
|
if (hasPermission) {
|
|
// Second onboarding gate (v1.4, one-time): reminder notifications.
|
|
// Null until DataStore's first emission — render nothing for that
|
|
// frame instead of flashing the wrong screen.
|
|
val reminderOnboarding: ReminderOnboardingViewModel = hiltViewModel()
|
|
val onboardingDone by reminderOnboarding.onboardingDone.collectAsStateWithLifecycle()
|
|
when (onboardingDone) {
|
|
true -> CalendarHost(
|
|
modifier = modifier,
|
|
requestedDetailKey = requestedDetailKey,
|
|
onDetailKeyConsumed = onDetailKeyConsumed,
|
|
widgetNavRequest = widgetNavRequest,
|
|
onWidgetNavConsumed = onWidgetNavConsumed,
|
|
requestedImportUri = requestedImportUri,
|
|
onImportConsumed = onImportConsumed,
|
|
)
|
|
false -> ReminderOnboardingScreen(
|
|
onFinished = reminderOnboarding::finish,
|
|
modifier = modifier,
|
|
)
|
|
null -> {}
|
|
}
|
|
} else {
|
|
PermissionScreen(
|
|
onGranted = { hasPermission = true },
|
|
modifier = modifier,
|
|
)
|
|
}
|
|
}
|