feat: multi-day event bars, readiness-gated splash, top-bar spinner, 2-line titles, detail animation

- Month view now draws multi-day events as continuous bars (lane packing,
  per-week column span clipped at week boundaries, +N overflow) instead of a
  chip on every day; events bucketed per week once + memoized packing for
  smooth scrolling
- Startup: core-splashscreen covers the window from frame 0 (no warm-start
  flash); the in-app splash stays until the first events finish loading (loading
  starts behind the splash via an early-obtained CalendarViewModel + ready flow),
  so you no longer enter a laggy app
- Background load shows a small spinner in the top bar (removed the linear bar)
- Week/Day titles wrap to two smaller lines (no more truncation)
- Event detail opens with a slide+fade animation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido Schmit
2026-05-31 14:40:23 +02:00
parent b8eb6597ec
commit 3734e17c3f
9 changed files with 232 additions and 108 deletions

View File

@@ -16,29 +16,37 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.scarriffle.calendarr.ui.auth.LoginScreen
import com.scarriffle.calendarr.ui.auth.ServerSetupScreen
import com.scarriffle.calendarr.ui.calendar.CalendarScreen
import com.scarriffle.calendarr.ui.calendar.CalendarViewModel
import com.scarriffle.calendarr.ui.theme.CalendarrTheme
import kotlinx.coroutines.delay
@Composable
fun CalendarrRoot(vm: MainViewModel = hiltViewModel()) {
val route by vm.route.collectAsState()
val settings by vm.settings.collectAsState()
var showSplash by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
kotlinx.coroutines.delay(1200)
showSplash = false
}
CalendarrTheme(settings) {
CompositionLocalProvider(
LocalLang provides L10n.resolved(settings.language),
LocalAppSettings provides settings,
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
// Obtain the calendar VM early when logged in so events load *behind* the splash.
val calendarVm: CalendarViewModel? =
if (route == AppRoute.MAIN) hiltViewModel() else null
val dataReady = calendarVm?.ready?.collectAsState()?.value ?: true
var minElapsed by remember { mutableStateOf(false) }
var timedOut by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { delay(450); minElapsed = true }
LaunchedEffect(Unit) { delay(6000); timedOut = true }
val showSplash = !minElapsed || (!dataReady && !timedOut)
if (showSplash) {
SplashScreen()
return@Surface
}
when (route) {
AppRoute.SETUP -> ServerSetupScreen(onConfigured = vm::onServerConfigured)
AppRoute.LOGIN -> LoginScreen(
@@ -47,6 +55,7 @@ fun CalendarrRoot(vm: MainViewModel = hiltViewModel()) {
onBack = vm::switchServer,
)
AppRoute.MAIN -> CalendarScreen(
vm = calendarVm!!,
onLogout = vm::logout,
onSwitchServer = vm::switchServer,
onSettingsChanged = vm::applyLocalSettings,