[User Availability] Implement user availability UI and networking stubs. - #92
conniecliu wants to merge 12 commits into
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📝 WalkthroughWalkthroughA new availability management feature is introduced, consisting of a Retrofit API service layer with data models, a repository for time-slot transformation, a ViewModel for state management, and Compose UI screens including calendar and filter panels. Related UI components for switches and checkboxes are extracted as reusable utilities, and the notification settings screen is refactored to use the new switch component. Changes
Sequence DiagramsequenceDiagram
actor User
participant Screen as AvailabilityScreen
participant ViewModel as AvailabilityViewModel
participant Repo as AvailabilityRepository
participant API as AvailabilityApiService
participant Server as Remote API
User->>Screen: Opens availability feature
Screen->>ViewModel: Loads on init
ViewModel->>Repo: getMyAvailability()
Repo->>API: getMyAvailability()
API->>Server: GET /availability/
Server-->>API: UserAvailability + schedule
API-->>Repo: AvailabilityResponse
Repo->>Repo: Convert schedule to List<LocalDateTime>
Repo-->>ViewModel: List<LocalDateTime>
ViewModel->>ViewModel: Update selectedAvailabilities & UI state
ViewModel-->>Screen: Display loaded slots
User->>Screen: Selects date range in MonthCalendar
Screen->>ViewModel: setSelectedAvailabilities(slots)
ViewModel->>ViewModel: Update state
User->>Screen: Clicks "Save" button
Screen->>ViewModel: saveAvailability()
ViewModel->>Repo: updateAvailability(selectedSlots)
Repo->>Repo: Transform List<LocalDateTime> to schedule map
Repo->>API: updateAvailability(UpdateAvailabilityRequest)
API->>Server: POST /availability/update/
Server-->>API: Updated UserAvailability
API-->>Repo: AvailabilityResponse
Repo-->>ViewModel: UserAvailability
ViewModel->>ViewModel: Update saveSuccess & UI state
ViewModel-->>Screen: Display success/error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (5)
app/src/main/res/drawable/ic_hamburger.xml (1)
8-8: Avoid hardcoded icon color in drawable.Using
#1E1E1Edirectly makes this asset less theme-aware (dark mode / dynamic theming). Prefer tinting at usage sites or using a theme-backed color resource.Also applies to: 11-11, 14-14
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/res/drawable/ic_hamburger.xml` at line 8, The drawable ic_hamburger.xml currently hardcodes android:fillColor="#1E1E1E" (also at the other occurrences noted), which prevents theme-aware coloring; remove the hardcoded fillColor entries and make the vector drawable colorless (or use android:fillColor="?android:attr/colorControlNormal" / a theme attribute), then apply tinting at usage sites (ImageView/AppCompatImageButton via android:tint or app:tint or via MaterialComponents theme attributes) so the icon respects light/dark and dynamic theming.app/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellSwitchRow.kt (1)
42-54: Avoid coupling visualcheckedstate toenabled.
checked = checked && enabledmakes the thumb render in the unchecked position wheneverenabled = false, even when the underlying value istrue. Material3'sSwitchalready renders a distinct disabled appearance via theenabledparameter (andSwitchDefaultsexposesdisabledCheckedTrackColor/disabledUncheckedTrackColoretc.). Maskingcheckedhere hides the real state from the user and from accessibility services (theRole.Switchsemantics will reportOffwhen the model isOn).Consider passing
checkedstraight through and adding the disabled color slots if you want a different look for the disabled state.♻️ Proposed change
Switch( - checked = checked && enabled, + checked = checked, onCheckedChange = onCheckedChange, colors = SwitchDefaults.colors( checkedThumbColor = Color.White, uncheckedThumbColor = IconInactive, checkedTrackColor = ResellPurple, uncheckedTrackColor = Color.White, checkedBorderColor = ResellPurple, uncheckedBorderColor = IconInactive ), enabled = enabled, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellSwitchRow.kt` around lines 42 - 54, The Switch in ResellSwitchRow.kt currently passes checked = checked && enabled which masks the true model state when the control is disabled; change it to checked = checked (do not combine with enabled) and instead customize the disabled appearance by supplying the appropriate disabled color slots via SwitchDefaults.colors (e.g., disabledCheckedTrackColor, disabledUncheckedTrackColor, disabledCheckedThumbColor/disabledUncheckedThumbColor or the equivalent properties you need) while leaving enabled = enabled; keep onCheckedChange and other props the same so accessibility and semantics reflect the real checked value.app/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.kt (2)
87-97: Optional: guard against concurrent saves and consume the server response.Two thoughts for follow-up, not blocking:
- Tapping Save twice in quick succession (or while a load is in flight) launches overlapping coroutines and produces racing POSTs. A simple guard via
stateValue().isLoadingearly-return — or by holding the in-flightJob— would prevent that.updateAvailabilityreturns the canonicalUserAvailabilityfrom the server, but it's discarded. If the backend normalizes/merges/rejects-partial slots, the client state silently drifts from the server. Consider applyingresult.toLocalDateTimes()toselectedAvailabilitieson success.♻️ Sketch
fun saveAvailability() { + if (stateValue().isLoading) return viewModelScope.launch { applyMutation { copy(isLoading = true, saveSuccess = false) } try { - availabilityRepository.updateAvailability(stateValue().selectedAvailabilities) - applyMutation { copy(isLoading = false, saveSuccess = true) } + val updated = availabilityRepository.updateAvailability(stateValue().selectedAvailabilities) + applyMutation { + copy( + selectedAvailabilities = updated.toLocalDateTimes(), + isLoading = false, + saveSuccess = true, + errorMessage = null, + ) + } } catch (e: Exception) { applyMutation { copy(isLoading = false, errorMessage = e.message) } } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.kt` around lines 87 - 97, The saveAvailability function can launch overlapping requests and currently discards the server's canonical result; fix by early-return if stateValue().isLoading is true (or store the launched Job and check its isActive) at the start of saveAvailability to prevent concurrent saves, and on successful call to availabilityRepository.updateAvailability(...) capture the returned UserAvailability and map it (e.g., call result.toLocalDateTimes()) to update selectedAvailabilities via applyMutation while still toggling isLoading and saveSuccess appropriately; ensure the error path still clears isLoading and sets errorMessage.
21-37: Sub-calendar state initialization.
subCalendarsdefaults toemptyList()whileenabledSubCalendarsdefaults toemptySet(). Until the Google Calendar API wiring lands (per the TODO), the filters panel will render an empty list. The PR's own preview screenshot, though, shows four named sub-calendars — so for the preview/interactive testing path you may want a temporary default list or a preview-only state to avoid an empty section while the screen is being demoed. Just flagging — feel free to defer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.kt` around lines 21 - 37, AvailabilityUiState currently initializes subCalendars to emptyList() and enabledSubCalendars to emptySet(), which leaves the filters panel empty in previews; to fix, provide temporary preview defaults (e.g., a list of sample calendar names and a matching enabled set) or add a preview-only constructor/flag to populate subCalendars and enabledSubCalendars for interactive/testing flows so the UI shows the four demo calendars; update AvailabilityUiState (the data class) to accept or set those preview defaults and ensure enabledSubCalendars contains the IDs/names that should be toggled on for the preview.app/src/main/java/com/cornellappdev/resell/android/model/api/AvailabilityApiService.kt (1)
18-36: Optional: extract DTOs to a separate file.Co-locating four
data classDTOs in the Retrofit service file works, but moving them into a siblingAvailabilityModels.kt(ormodel/availability/package) keeps the service interface focused and matches typical separation-of-concerns conventions. Defer to existing repo style.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/cornellappdev/resell/android/model/api/AvailabilityApiService.kt` around lines 18 - 36, Extract the four DTO data classes (AvailabilityResponse, UserAvailability, AvailabilitySlot, UpdateAvailabilityRequest) out of the Retrofit service file into a new Kotlin file (e.g., AvailabilityModels.kt or under a model/availability package) so the AvailabilityApiService stays focused; keep the classes’ names and fields unchanged, place them in the same package as the service (or adjust package/imports accordingly), and update the AvailabilityApiService imports/usages to reference the moved classes. Ensure no behavior changes and that serialization annotations or visibility modifiers (if any) are preserved during the move.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/cornellappdev/resell/android/model/api/AvailabilityApiService.kt`:
- Around line 18-36: The model classes (AvailabilityResponse, UserAvailability,
AvailabilitySlot, UpdateAvailabilityRequest) need Gson `@SerializedName`
annotations for fields that the backend uses in snake_case: add
`@SerializedName`("user_id") to userId, `@SerializedName`("updated_at") to
updatedAt, `@SerializedName`("start_date") and `@SerializedName`("end_date") to
startDate/endDate in AvailabilitySlot, and add `@SerializedName` annotations as
needed for id and schedule (e.g., "id" and "schedule") so
GsonConverterFactory.create() can correctly map JSON keys to the Kotlin
properties; update the data classes to include these annotations on the
corresponding properties.
In
`@app/src/main/java/com/cornellappdev/resell/android/model/profile/AvailabilityRepository.kt`:
- Around line 22-32: Update AvailabilityRepository so the datetime formatter
matches the parser used by AvailabilityViewModel.toLocalDateTimes(): replace
DateTimeFormatter.ISO_LOCAL_DATE_TIME with a formatter that includes
offset/timezone (e.g., DateTimeFormatter.ISO_OFFSET_DATE_TIME or the same
formatter used by toLocalDateTimes()) when formatting startDate and endDate for
AvailabilitySlot; also replace daySlots.sortedBy { it } with daySlots.sorted()
since LocalDateTime is Comparable. Extract the hardcoded 30L into a shared
constant (e.g., SLOT_DURATION_MINUTES) and use that constant in
AvailabilityRepository (endDate calculation), AvailabilityUtil,
SelectableAvailabilityGrid, ViewOnlyAvailabilityGrid, and test helpers so all
code references the same slot duration.
In
`@app/src/main/java/com/cornellappdev/resell/android/ui/components/availability/helper/AvailabilityFilters.kt`:
- Around line 78-80: The checkedStates mutable map is currently local to
AvailabilityFilters and reset when the composable is removed; move its state
into AvailabilityViewModel (add a MutableState<Map<String,Boolean>> or similar)
and expose it via state + update callback parameters to AvailabilityFilters
(mirror the pattern used by NotificationSettings). In practice: remove remember
{ mutableStateMapOf... } from AvailabilityFilters, add properties and updater
functions in AvailabilityViewModel (e.g., checkedStates, setCheckedState(key,
value)), also hoist the two switch values into the ViewModel with corresponding
setters, and update AvailabilityFilters to accept the current states and
callbacks so the TODO switch handlers call the supplied setters rather than
mutating local state.
In
`@app/src/main/java/com/cornellappdev/resell/android/ui/components/availability/helper/MonthCalendar.kt`:
- Around line 24-30: MonthCalendar declares onMonthChange but never calls it, so
wire month navigation controls to invoke it: add previous/next buttons or
chevrons in the MonthCalendar header (matching Figma) and call
onMonthChange(currentMonth.minusMonths(1)) for the previous control and
onMonthChange(currentMonth.plusMonths(1)) for the next control; ensure the
clickable composables update accessibility/tap targets and preserve the existing
parameters (currentMonth, selectedDates) when rendering, or if you prefer not to
implement navigation now, remove the onMonthChange parameter from MonthCalendar
and its callers (e.g., AvailabilityScreen ->
availabilityViewModel.setCurrentMonth) to avoid a dead callback.
In
`@app/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellCheckbox.kt`:
- Around line 41-74: Move the toggleable modifier off the inner Box and onto the
Row (or add Modifier.semantics) so the entire Row toggles and accessibility
announcements include the label; add semantics/stateDescription or
contentDescription that uses the title and the checked state (e.g., "$title,
${if (checked) "checked" else "unchecked"}") so TalkBack reads the name and
state. Also make the visual styling respect enabled by changing how fillColor
and borderColor are computed in ResellCheckbox (use disabled variants or apply
alpha when enabled == false) and ensure the Icon tint/opacity follows enabled as
well; remove duplicate toggleable on the Box if moved. Ensure Role.Checkbox
remains on the toggleable call for accessibility.
In
`@app/src/main/java/com/cornellappdev/resell/android/ui/screens/main/AvailabilityScreen.kt`:
- Around line 216-220: The preview crashes because AvailabilityScreen() calls
hiltViewModel(), which isn't available in the IDE preview; update the preview to
not resolve a Hilt VM — either replace AvailabilityScreenPreview to render the
hoisted UI (call AvailabilityScreenContent(...) with a mocked
AvailabilityViewState and no-op callbacks) or change AvailabilityScreen
signature to accept viewModel: AvailabilityViewModel? = null and short-circuit
when viewModel is null so the preview can pass a fake/null viewModel; reference
AvailabilityScreenPreview, AvailabilityScreen, hiltViewModel,
AvailabilityScreenContent, and AvailabilityViewModel when making the change.
- Around line 70-71: The code in AvailabilityScreen.kt incorrectly sets
firstOfWeek = availabilityUiState.currentMonth.atDay(1) which yields the 1st–3rd
of the month; change the logic so the 3-day window is derived from the user's
selected anchor date or selectedDates range (e.g., use
availabilityUiState.selectedDates.firstOrNull() or an anchorDate from the view
state, falling back to currentMonth.atDay(1) only as a last resort), rename
firstOfWeek to a clearer name like windowStartDate, and build dates = (0..2).map
{ windowStartDate.plusDays(it.toLong()) } so the visible 3-day grid follows the
user selection/Mont hCalendar rather than always the start of the month.
In
`@app/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.kt`:
- Around line 70-97: In loadAvailability and saveAvailability clear stale UI
flags on success: when you call applyMutation in the successful branches of
loadAvailability and saveAvailability, explicitly reset errorMessage to null (or
empty) and ensure saveSuccess is set appropriately (e.g., true after save, and
cleared to false when loading new data). Specifically update the applyMutation
calls in loadAvailability to set errorMessage = null and saveSuccess = false
when loading finishes successfully, and update the applyMutation in
saveAvailability to set errorMessage = null alongside saveSuccess = true; keep
existing isLoading handling. This ensures state produced by applyMutation (and
observed via stateValue()/selectedAvailabilities) doesn't retain old error or
success flags after subsequent successful operations.
- Around line 105-108: Change the DateTimeFormatter used in
UserAvailability.toLocalDateTimes to match the repository serialization: replace
DateTimeFormatter.ISO_DATE_TIME with DateTimeFormatter.ISO_LOCAL_DATE_TIME when
parsing slot.startDate in the toLocalDateTimes() function so parsing
consistently uses the local-date-time format used by
AvailabilityRepository.updateAvailability.
In `@app/src/main/res/drawable/ic_hamburger.xml`:
- Around line 2-5: The icon's intrinsic size is set too large; update the
android:width and android:height attributes in ic_hamburger.xml from 204dp x
186dp to the standard icon size (e.g., 24dp x 24dp) while leaving the
android:viewportWidth, android:viewportHeight and the existing path data
unchanged so the visual scales correctly to your design-system icon size.
---
Nitpick comments:
In
`@app/src/main/java/com/cornellappdev/resell/android/model/api/AvailabilityApiService.kt`:
- Around line 18-36: Extract the four DTO data classes (AvailabilityResponse,
UserAvailability, AvailabilitySlot, UpdateAvailabilityRequest) out of the
Retrofit service file into a new Kotlin file (e.g., AvailabilityModels.kt or
under a model/availability package) so the AvailabilityApiService stays focused;
keep the classes’ names and fields unchanged, place them in the same package as
the service (or adjust package/imports accordingly), and update the
AvailabilityApiService imports/usages to reference the moved classes. Ensure no
behavior changes and that serialization annotations or visibility modifiers (if
any) are preserved during the move.
In
`@app/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellSwitchRow.kt`:
- Around line 42-54: The Switch in ResellSwitchRow.kt currently passes checked =
checked && enabled which masks the true model state when the control is
disabled; change it to checked = checked (do not combine with enabled) and
instead customize the disabled appearance by supplying the appropriate disabled
color slots via SwitchDefaults.colors (e.g., disabledCheckedTrackColor,
disabledUncheckedTrackColor,
disabledCheckedThumbColor/disabledUncheckedThumbColor or the equivalent
properties you need) while leaving enabled = enabled; keep onCheckedChange and
other props the same so accessibility and semantics reflect the real checked
value.
In
`@app/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.kt`:
- Around line 87-97: The saveAvailability function can launch overlapping
requests and currently discards the server's canonical result; fix by
early-return if stateValue().isLoading is true (or store the launched Job and
check its isActive) at the start of saveAvailability to prevent concurrent
saves, and on successful call to availabilityRepository.updateAvailability(...)
capture the returned UserAvailability and map it (e.g., call
result.toLocalDateTimes()) to update selectedAvailabilities via applyMutation
while still toggling isLoading and saveSuccess appropriately; ensure the error
path still clears isLoading and sets errorMessage.
- Around line 21-37: AvailabilityUiState currently initializes subCalendars to
emptyList() and enabledSubCalendars to emptySet(), which leaves the filters
panel empty in previews; to fix, provide temporary preview defaults (e.g., a
list of sample calendar names and a matching enabled set) or add a preview-only
constructor/flag to populate subCalendars and enabledSubCalendars for
interactive/testing flows so the UI shows the four demo calendars; update
AvailabilityUiState (the data class) to accept or set those preview defaults and
ensure enabledSubCalendars contains the IDs/names that should be toggled on for
the preview.
In `@app/src/main/res/drawable/ic_hamburger.xml`:
- Line 8: The drawable ic_hamburger.xml currently hardcodes
android:fillColor="#1E1E1E" (also at the other occurrences noted), which
prevents theme-aware coloring; remove the hardcoded fillColor entries and make
the vector drawable colorless (or use
android:fillColor="?android:attr/colorControlNormal" / a theme attribute), then
apply tinting at usage sites (ImageView/AppCompatImageButton via android:tint or
app:tint or via MaterialComponents theme attributes) so the icon respects
light/dark and dynamic theming.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 51cceb0c-2327-4a46-8e0b-6d3f15f94c35
📒 Files selected for processing (11)
app/src/main/java/com/cornellappdev/resell/android/model/api/AvailabilityApiService.ktapp/src/main/java/com/cornellappdev/resell/android/model/api/RetrofitInstance.ktapp/src/main/java/com/cornellappdev/resell/android/model/profile/AvailabilityRepository.ktapp/src/main/java/com/cornellappdev/resell/android/ui/components/availability/helper/AvailabilityFilters.ktapp/src/main/java/com/cornellappdev/resell/android/ui/components/availability/helper/MonthCalendar.ktapp/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellCheckbox.ktapp/src/main/java/com/cornellappdev/resell/android/ui/components/global/ResellSwitchRow.ktapp/src/main/java/com/cornellappdev/resell/android/ui/screens/main/AvailabilityScreen.ktapp/src/main/java/com/cornellappdev/resell/android/ui/screens/settings/NotificationSettings.ktapp/src/main/java/com/cornellappdev/resell/android/viewmodel/main/AvailabilityViewModel.ktapp/src/main/res/drawable/ic_hamburger.xml
AndrewCheung360
left a comment
There was a problem hiding this comment.
Nice work! Just left some minor comments, and some of the coderabbit comments could probably be addressed if they make sense, especially if it is critical
…t selected days to viewmodel, put days into fixed groups of 3s, extract colors into Color.kt
…in AvailabilityViewModel.kt, and parse times correctly, making sure converting to/from UTC and local are not lost
…es. In the case of 6 rows, the calendar will be scrollable.
Overview
Implemented the User Availability feature based on these designs.
This PR adds a fully completed UI, a view model for the Availability Screen, Resell-specific composables, and networking stubs.
Changes Made
AvailabilityScreen.ktwith active panel control on the UIMonthCalendar.ktandAvailabilityFilters.ktTest Coverage
Next Steps
MonthCalendar,AvailabilityFilters)Related PRs or Issues
Note: the above PR implements the changes to the User Availability feature introduced on the Profile Screen. It's the upstream screen that leads to the implementation in this PR.
Screenshots and recordings
Availability Screen UI + Other Composables
Screen.Recording.2026-04-27.at.12.05.19.AM.mov
Summary by CodeRabbit
New Features
Refactor