Realays Logo Realays
← Back to Blog
DevLog 11/26/2025

[Dalendar DevLog 4] UI Implementation - Schedule Creation and Editing

Building intuitive UI for schedule management with custom date/time pickers and material design.

[Dalendar DevLog 4] UI Implementation - Schedule Creation and Editing

Episode 4: UI Implementation - Schedule Creation and Editing

Introduction

With our solid data layer in place, it’s time to build the user-facing features. This episode covers designing and implementing an intuitive interface for creating, editing, and managing schedules.

1. Material Design Implementation

We follow Material Design 3 principles for a modern, familiar user experience:

1.1. Schedule Creation FAB

FloatingActionButton(
    onClick = { showScheduleDialog = true },
    containerColor = MaterialTheme.colorScheme.primaryContainer
) {
    Icon(Icons.Default.Add, "Add Schedule")
}

1.2. Bottom Sheet Dialog

For schedule creation, we use a modal bottom sheet for easy access without leaving the calendar view:

ModalBottomSheet(
    onDismissRequest = { showDialog = false }
) {
    ScheduleForm(
        onSave = { schedule ->
            viewModel.saveSchedule(schedule)
            showDialog = false
        }
    )
}

2. Custom Date Picker

2.1. Rata Die Integration

Our date picker uses Rata Die internally for efficient date selection:

@Composable
fun RataDieDatePicker(
    initialRataDie: Int,
    onDateSelected: (Int) -> Unit
) {
    var selectedRataDie by remember { mutableStateOf(initialRataDie) }

    DatePicker(
        state = rememberDatePickerState(
            initialSelectedDateMillis = rataDieToMillis(selectedRataDie)
        )
    ) { millis ->
        selectedRataDie = millisToRataDie(millis)
        onDateSelected(selectedRataDie)
    }
}

2.2. Performance Optimization

Converting between Rata Die and display formats is optimized using our EAF calculations:

fun rataDieToYMD(rataDie: Int): Triple<Int, Int, Int> {
    // Ultra-fast EAF conversion
    val n = rataDie - EPOCH_OFFSET
    val y = calculateYear(n)
    val m = calculateMonth(n, y)
    val d = calculateDay(n, y, m)
    return Triple(y, m, d)
}

3. Time Picker Implementation

3.1. 12/24 Hour Format Support

@Composable
fun CustomTimePicker(
    initialTime: String? = null,
    use24HourFormat: Boolean = false,
    onTimeSelected: (String) -> Unit
) {
    val timeState = rememberTimePickerState(
        is24Hour = use24HourFormat
    )

    TimePicker(state = timeState)

    // Format as HH:mm string
    val timeString = "${timeState.hour}:${timeState.minute.toString().padStart(2, '0')}"
    onTimeSelected(timeString)
}

4. Recurrence UI Builder

4.1. Recurrence Pattern Selector

Users can easily set up recurring events:

@Composable
fun RecurrenceSelector(
    onRecurrenceSet: (String?) -> Unit
) {
    var frequency by remember { mutableStateOf("NONE") }
    var interval by remember { mutableStateOf(1) }
    var selectedDays by remember { mutableStateOf(setOf<String>()) }

    Column {
        // Frequency dropdown
        DropdownMenu(
            options = listOf("NONE", "DAILY", "WEEKLY", "MONTHLY", "YEARLY")
        ) { freq -> frequency = freq }

        // Weekly day selection
        if (frequency == "WEEKLY") {
            DayOfWeekSelector(
                selectedDays = selectedDays,
                onDaysChanged = { selectedDays = it }
            )
        }

        // Generate recurrence rule string
        val rule = buildRecurrenceRule(frequency, interval, selectedDays)
        onRecurrenceSet(rule)
    }
}

4.2. Visual Recurrence Display

fun formatRecurrenceRuleForDisplay(rule: String?): String {
    if (rule == null) return "Does not repeat"

    val freq = extractFrequency(rule)
    val interval = extractInterval(rule)

    return when (freq) {
        "DAILY" -> if (interval == 1) "Daily" else "Every $interval days"
        "WEEKLY" -> {
            val days = extractWeekDays(rule)
            "Weekly on ${days.joinToString(", ")}"
        }
        "MONTHLY" -> "Monthly"
        "YEARLY" -> "Yearly"
        else -> "Custom"
    }
}

5. Form Validation

5.1. Input Validation

data class ScheduleFormState(
    val title: String = "",
    val startRataDie: Int = todayRataDie(),
    val endRataDie: Int = todayRataDie(),
    val isAllDay: Boolean = true
) {
    val isTitleValid: Boolean
        get() = title.isNotBlank()

    val isDateRangeValid: Boolean
        get() = endRataDie >= startRataDie

    val isValid: Boolean
        get() = isTitleValid && isDateRangeValid
}

5.2. Error Display

OutlinedTextField(
    value = formState.title,
    onValueChange = { /* update */ },
    label = { Text("Title") },
    isError = !formState.isTitleValid,
    supportingText = {
        if (!formState.isTitleValid) {
            Text("Title is required")
        }
    }
)

6. Schedule Display on Calendar

6.1. Day Cell with Schedule Indicators

@Composable
fun DayCell(
    rataDie: Int,
    schedules: List<Schedule>,
    onClick: () -> Unit
) {
    Box(
        modifier = Modifier
            .aspectRatio(1f)
            .clickable(onClick = onClick)
    ) {
        // Date number
        Text(
            text = rataDieToDay(rataDie).toString(),
            style = MaterialTheme.typography.bodyMedium
        )

        // Schedule indicators (dots)
        Row(
            modifier = Modifier.align(Alignment.BottomCenter)
        ) {
            schedules.take(3).forEach { schedule ->
                Box(
                    modifier = Modifier
                        .size(4.dp)
                        .background(
                            color = Color(schedule.color),
                            shape = CircleShape
                        )
                )
            }
        }
    }
}

6.2. Day Detail View

When a user clicks a day with schedules:

@Composable
fun DayDetailSheet(
    rataDie: Int,
    schedules: List<Schedule>,
    onDismiss: () -> Unit
) {
    ModalBottomSheet(onDismissRequest = onDismiss) {
        Column {
            Text(
                text = formatDate(rataDie),
                style = MaterialTheme.typography.headlineSmall
            )

            LazyColumn {
                items(schedules) { schedule ->
                    ScheduleListItem(
                        schedule = schedule,
                        onClick = { /* edit */ }
                    )
                }
            }
        }
    }
}

7. Edit and Delete Operations

7.1. Edit Dialog

fun editSchedule(schedule: Schedule) {
    showEditDialog = true
    editingSchedule = schedule
}

ScheduleForm(
    initialSchedule = editingSchedule,
    onSave = { updated ->
        viewModel.updateSchedule(updated)
        showEditDialog = false
    }
)

7.2. Delete Confirmation

AlertDialog(
    onDismissRequest = { showDeleteDialog = false },
    title = { Text("Delete Schedule?") },
    text = { Text("This action cannot be undone.") },
    confirmButton = {
        TextButton(
            onClick = {
                viewModel.deleteSchedule(scheduleToDelete)
                showDeleteDialog = false
            }
        ) {
            Text("Delete")
        }
    },
    dismissButton = {
        TextButton(onClick = { showDeleteDialog = false }) {
            Text("Cancel")
        }
    }
)

8. Performance Considerations

8.1. LazyColumn for Schedule Lists

LazyColumn {
    items(
        items = schedules,
        key = { it.id }
    ) { schedule ->
        ScheduleListItem(schedule)
    }
}

8.2. Remember and MemoizedState

val schedules by viewModel.schedulesForDay(rataDie)
    .collectAsState(initial = emptyList())

val sortedSchedules = remember(schedules) {
    schedules.sortedWith(
        compareBy({ !it.isAllDay }, { it.startTime })
    )
}

9. Accessibility

9.1. Content Descriptions

Icon(
    Icons.Default.Add,
    contentDescription = "Add new schedule"
)

9.2. Semantic Properties

Box(
    modifier = Modifier.semantics {
        contentDescription = "Schedule for ${formatDate(rataDie)}"
        role = Role.Button
    }
)

10. Animation and Transitions

10.1. Dialog Animations

AnimatedVisibility(
    visible = showDialog,
    enter = slideInVertically() + fadeIn(),
    exit = slideOutVertically() + fadeOut()
) {
    ScheduleDialog()
}

10.2. List Item Animations

AnimatedContent(
    targetState = schedule,
    transitionSpec = {
        slideInHorizontally() with slideOutHorizontally()
    }
) { targetSchedule ->
    ScheduleListItem(targetSchedule)
}

Conclusion

We’ve built a comprehensive schedule management UI featuring:

✅ Material Design 3 components ✅ Custom date/time pickers with Rata Die integration ✅ Recurrence pattern builder ✅ Form validation and error handling ✅ Schedule indicators on calendar ✅ Edit/delete operations with confirmations ✅ Performance optimizations ✅ Accessibility support ✅ Smooth animations

In the next episode, we’ll implement advanced features like schedule search, filters, and calendar synchronization.

Next Episode Preview:

  • Search and filter functionality
  • Calendar import/export
  • Notification system
  • Widget implementation

The app is coming together beautifully!

Related Posts