-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement Calendar Screen with event handling, tests, and updated dependencies #25
Conversation
- Add CalendarScreen composable to display a calendar view - Allow users to select dates and view events dynamically - Implement event lookup functionality with search and dialog for filtering events by name - Handle multi-day events and indicate event occurrences visually in the calendar - Display detailed event information including title and date in event lists
…ions - Adjustments to project dependencies for calendar and testing features - Update necessary plugins and build configurations
- Add ical4j version 3.0.21 for iCal integration - Add Compose version 1.5.1 for runtime-livedata integration
- Verify the display of current month in the calendar header - Add tests for navigating between months using the previous and next buttons - Ensure the 'Look Up Event' button is visible and functional - Test that selecting a day updates the selected date in the calendar - Set up a basic structure for additional tests as Figma updates
- Implement model classes to support calendar feature integration - Define necessary structures for handling iCal data and event processing
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job, the code is well-structured, and the calendar feature looks great in the emulator. A few suggestions:
It would be great to add unit tests for the CalendarViewModel to cover its core functionalities, like filtering and sorting events, and also increase test coverage for the CalendarScreen.
Centering the "Look Up Event" button would improve the layout, and you could maybe add a little prompt that better describes the utility of the button (which might help users understand its purpose better).
Aside from these, everything looks solid. Great work!
Hey, thanks for the feedback! I’ll definitely add some tests for CalendarViewModel to cover its functionalities like you mentioned, and I’ll also take a look at the UI tests to improve the coverage. For the "Look Up Event" button, I’m not sure about centering it. I feel like it might throw off the layout a bit. I was thinking of switching it to a button with a magnifying glass icon instead. It could make it more intuitive for users. What do you think? Appreciate the suggestions! I’ll make these changes and push an update soon. |
I'll go ahead and merge this pull request to allow others to continue working on their parts of the project. The requested changes will be implemented as soon as possible, and I'll create a new issue to track those updates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall the PR is pretty good, the description is exhaustive (although it could be a bit shorter), the code is overall pretty good, with a good structure.
However, the PR was merged even though a PR review requested changes (and was right to do so). The Request changes option acts as a big stop sign, you should always wait for an Approve review.
CalendarViewModel
- It is a good use of a view model, it is short but well designed.
CalendarScreen
- There is a good modularity of the components:
CalendarScreen
,SearchDialog
,CalendarHeader
,EventItem
are all great. - Good use of the
MutableState
for UI states (selectedDate
,searchQuery
, ...) - Good use of callbacks (
onDateSelected
,onEventClick
, ...)
Other
- You could consider creating a wrapper on
VEvent
as it pretty cumbersome to always having to get the property, perform null check, ...
This PR review is just feedback that we give you, if I chose this PR amongst others, it is because I have things to say on it, not because it is bad (it is not bad).
|
||
private val client = OkHttpClient() | ||
private val _icalEvents = mutableStateListOf<VEvent>() | ||
val icalEvents: List<VEvent> = _icalEvents |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should use StateFlow
in your view models (you can look into the bootcamp solution for examples):
private val _icalEvents = MutableStateFlow<List<VEvent>>(listOf())
val icalEvents: StateFlow<VEvent> = query_
Sidenote when using stateflows: always use immutable structures as it only reacts to reference changes, for example:
// good
_icalEvents.value = newList
// bad
_icalEvents.value.addAll(newList)
viewModelScope.launch { | ||
val icalData = | ||
fetchIcalFromUrl( | ||
"https://p127-caldav.icloud.com/published/2/MTE0OTM4OTk2MTExNDkzOIzDRaBjEGa9_1mlmgjzcdlka5HK6EzMiIdOswU-0rZBZMDBibtH_M7CDyMpDQRQJPdGOSM0hTsS2qFNGOObsTc") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid magical values, especially when they are not trivial (1
is, MTE0OTM
is not), something like this would be clearer:
fetchIcalFromUrl(ICAL_URL)
while (current.before(eventEndDate)) { | ||
val multiDayEventInstance = eventInstance.copy() as VEvent | ||
multiDayEventInstance.getProperty<DtStart>(Property.DTSTART)?.date = | ||
DateTime(current.time) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods handleRecurringEvents
is missing documentation, both for the method itself and in the code, for example: why do we need to make a copy of eventInstance
and change its DTSTART
property ? In addition, the code structure is complicated with a while loop inside an if inside a for loop, consider refactoring or adding comments.
Also, the function takes a mutable list allEvents
as argument and modify it, while this is good for performance (i.e. it's what you would do in C), it is something you want to avoid, as the function is then performing side-effects, which are known to introduce bugs and be harder to reason about.
Here is an example of how handleRecurringEvents
could be refactored:
/**
* General doc about the function
*/
private fun handleRecurringEvents(
component: VEvent,
period: Period,
eventStartDate: Date,
eventEndDate: Date,
): List<VEvent> {
val events = mutableListOf<VEvent>()
val recurrenceSet = component.calculateRecurrenceSet(period)
for (recurrence in recurrenceSet) {
val eventInstance = component.copy() as VEvent
eventInstance.getProperty<DtStart>(Property.DTSTART)?.date = recurrence.start
if (!eventEndDate.after(recurrence.start)) {
// comment on why no while loop
events.add(eventInstance)
continue
}
var current = recurrence.start
while (current.before(eventEndDate)) {
// comment on why this
val multiDayEventInstance = eventInstance.copy() as VEvent
multiDayEventInstance.getProperty<DtStart>(Property.DTSTART)?.date =
DateTime(current.time)
events.add(multiDayEventInstance)
// comment add a day, or better use an helper function
val calendarInstance =
JavaCalendar.getInstance().apply {
time = current
add(JavaCalendar.DATE, 1)
}
current = DateTime(calendarInstance.time)
}
}
return events
}
} | ||
} | ||
|
||
private fun handleNonRecurringEvents( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as for handleRecurringEvents
method, it is missing documentation and taking a mutable list as argument.
_icalEvents.clear() | ||
_icalEvents.addAll(allEvents) | ||
Log.d("CalendarViewModel", "Total events parsed: ${_icalEvents.size}") | ||
} catch (e: Exception) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This try-catch block is very long and it catches all exceptions subtype of Exception
, try to isolate the code which may throw the exception and restrict the catch block to catch more specific exception if applicable (for example IOException
)
} | ||
|
||
@Composable | ||
fun EventList(calendarViewModel: CalendarViewModel, selectedDate: Date) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having both EventListWithResults
and EventList
components, you could have one component that would take a title, list of events, and onEventClick
as arguments, to have a more modular approach, something like that:
@Composable
fun EventList(title: String, events: List<VEvent>, onEventClick: (Date) -> Unit) {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp))
if (events.isNotEmpty()) {
events.forEach { event ->
val eventDate = event.getProperty<DtStart>(Property.DTSTART)?.date as Date
EventItem(event = event, onClick = { onEventClick(eventDate) })
Spacer(modifier = Modifier.height(8.dp))
}
} else {
Text(
text = "No events.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
}
fun CalendarGrid( | ||
selectedDate: Date, | ||
onDateSelected: (Date) -> Unit, | ||
calendarViewModel: CalendarViewModel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing the view model instance to then use the icalEvents
attribute, consider passing a list of VEvent
, icalEvents
. The idea is to pass the least general object down, as it then clear that CalendarGrid
only need the ical events list to someone not familiar with CalendarGrid
and it is simplier to test.
|
||
val isSelected = isSameDay(date, selectedDate) | ||
|
||
Box( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider creating a component here, to simplify the CalendarGrid
method.
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) | ||
} | ||
|
||
fun updateMonth(date: Date, months: Int): Date { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider creating a DateUtils
module to group all helper functions for dates, so that they can be reused in other files. That way you can also keep track of all already defined helper functions to avoid duplicates.
composeTestRule.onNodeWithText("Look Up Event").assertIsDisplayed() | ||
} | ||
|
||
// Add more tests when figma is updated with all the components and testTags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing more permanent than a temporary solution - Oswald Maskens (I must give credits to the guy)
Either create the tests now or don't basically.
Description
This pull request introduces the implementation of the Calendar Screen feature with dynamic event handling, multi-day and recurring event support, as well as UI tests. The PR also includes necessary dependency updates in the build files.
Key Features:
Calendar Screen:
Event Handling:
UI Tests:
Dependency Updates:
ical4j
to version3.0.21
for iCal event handling.Changes:
CalendarScreen
with event display and month navigation.CalendarViewModel
.gradle/libs.versions.toml
with required dependencies.CalendarScreenTest.kt
for verifying calendar functionality.