Reconnect FCM and re-enable push notifications#141
Closed
bryantran24 wants to merge 3 commits into
Closed
Conversation
Collaborator
|
will merge after server implements fcm and is tested |
bryantran24
added a commit
that referenced
this pull request
Jul 21, 2026
…main's architecture refactor (#163) * establish top-level application package structure - move Application class into app/ - move MainActivity into app/ - move DI modules into app/di * reorganize API and repository architecture - move Retrofit endpoints into ShuttleApi - replace API helper with RemoteShuttleDataSource - organize network DTOs and mappers - move repository implementations into data/repository * move map UI into feature package * Organize active UI by feature * organize local preferences and migrate announcements feature * organize background components * remove FlattenTypeAdapterFactory and use explicit DTO mapping * Remove unused analytics * organize connectivity extension * resolve compose correctness warnings * upgrade toolchain and dependencies * migrate to Google OSS Licenses * Update announcements * Update setup feature * Update settings feature * Fix dev mode url change and add test * Reorganize maps feature * prevent crashes and clean up unused code * fix(ui): improve pre-QA screen stability * fix(network): harden API parsing and base URLs * fix(settings): reset setup flow safely * chore(build): enable release shrinking and trim dependencies * chore: remove unused UI resources and helpers * ci: run lint and release source checks * chore(build): enable Gradle build caching * chore(build): enable parallel Gradle tooling * refactor(data): clarify data and dependency injection names * test: add core JVM regression coverage * test(ui): add core Compose behavior coverage * refactor(navigation): migrate from Compose Destinations to Navigation 3 * refactor(network): remove NetworkResponseAdapter * refactor(ui): replace Compose icons with Material Symbols * refactor(network): replace Gson with Kotlin serialization * ci: chain build and test Gradle tasks * test cache * ci: improve Gradle caching in build and release workflows * test cache * Update GitHub Actions Node 24 actions * refactor(network): simplify API base URL configuration * refactor(announcements): adopt banner API contract Switch to the wrapped /announcements response with id/message/type/ active/expiresAt/createdAt fields, replacing the old subject/body/ start/end shape. Adds a type-safe AnnouncementType enum (falling back to info for unknown values) and tolerant timestamp parsing that handles offset/zoned ISO strings, non-zero-padded local timestamps interpreted in America/New_York, and malformed values without crashing. Displayability and severity/date ordering live on the model so both the legacy screen and the upcoming map banner can share them. Legacy announcement worker/screen/tests are updated to the new field names since they directly construct the old shape; they are removed outright in a later commit once the map banner replaces them. * feat(map): display active announcement banners Adds observeAnnouncements() to ShuttleRepository, polled by MapsViewModel on its own 5-minute interval (started/stopped with the map's lifecycle, independent of the 5 second vehicle polling flows). Results are filtered to active/unexpired and sorted by severity then recency before landing in MapsUiState; a failed refresh leaves the previously loaded banners in place. Adds a compact Material 3 banner container above the map: the most severe announcement shows by default with a "show more" affordance for the rest, each styled per type (error/warning/info) with both a color and an icon + text label so meaning doesn't depend on color alone. Markdown-style `[label](url)` links are parsed by a small regex-based utility (no Markdown dependency) and rendered as clickable text only when the URL is http/https; malformed markdown or unsafe schemes fall back to plain text without crashing. * refactor(notifications): remove API-driven push flow Removes AnnouncementWorker and its daily WorkManager job now that the map banner (previous commit) is the only surface for API announcements; API data must never produce a system notification. Also removes the now-legacy announcements screen/view model/nav route and NotificationReceiver's "mark as read" broadcast action, along with the notifications-read DataStore preference it fed. Drops the WorkManager/Hilt-work dependencies and the manifest's WorkManagerInitializer override since AnnouncementWorker was their only consumer, and prunes the strings/drawable that existed solely for the removed screen and notification action. FirebaseService's content intent is repointed at MainActivity directly (NotificationReceiver.openAnnouncements no longer exists) as a minimal stand-in; the full Firebase push redesign lands in the next commit. * feat(notifications): complete Firebase push handling Rewrites FirebaseService to show the Firebase message's own title and body on a dedicated push channel, using a per-message notification ID (from the message ID, falling back to the clock) instead of one constant ID that would silently replace every prior push. Tapping a notification opens the map - already the app's home screen - with an optional safe http/https `url` custom-data field opened externally instead; MainActivity resolves that in both onCreate and onNewIntent so cold-start and warm-start taps behave the same without duplicate navigation entries. Deliberately does not port PR #141's NotificationManager.cancelAll() on resume, since that would clear unrelated notifications. Adds manifest defaults (icon/color/channel) so Firebase Console messages displayed automatically while the app is backgrounded or killed - which never reach onMessageReceived - still get the right appearance and open the same tap target. Firebase is now fully decoupled from the shuttle API: removes sendRegistrationToken and the POST /notifications/fcmdevices endpoint, plus FirebaseService's ShuttleRepository injection and onNewToken override, since Firebase manages its own registration and staff send pushes manually from the Firebase Console. google-services.json is left untouched - its package name and project data are valid for this app. The url-safety check moves to a shared core/util location so both the map banner's Markdown links and the notification tap resolver use the same validation. * fix(announcements,maps): tolerate live API response and Play Services quirks The real /announcements endpoint returns a bare JSON array rather than the documented {"announcements": [...]} wrapper, which crashed every fetch with JsonDecodingException. AnnouncementsResponseDto now has a custom KSerializer that accepts either shape and decodes each entry independently so one malformed announcement can't sink the whole list. Also declares the org.apache.http.legacy library: Play Services' Maps renderer module still references the legacy Apache HTTP client, which was removed from the platform classpath in API 28+, crashing map load on some emulator images with NoClassDefFoundError. * feat(dev-tools): add FCM token logging and simulate-announcements toggle Adds a debug-only onNewToken log of the FCM registration token so it can be pasted into the Firebase Console's "Send test message" field, which targets one device rather than the whole app. Adds a "Simulate announcements" switch to the developer menu that feeds three fixed sample banners (one per severity, one with a Markdown link) into MapsViewModel instead of the live API response, for exercising the banner UI while the shuttle isn't running. Turning it off restarts the announcement poll for an immediate real fetch rather than waiting for the next 5-minute tick. Also updates google-services.json to the project actually used for manual push testing. * refactor(notifications): remove unused bus-tracking and departure channels CHANNEL_TRACKING_BUS and CHANNEL_FIRING_DEPARTURES (and their groups) were never wired to any feature and aren't planned for a while, so they only showed up as empty categories in system notification settings. Notifications now only registers the push channel; the old channel/group IDs are added to the cleanup lists so they get deleted from devices that already had them created. * refactor(map): redesign announcement UI as a top strip and detail sheet Replaces the stacked-cards-with-expand banner with a single-line severity strip (icon, truncated message, +N count, chevron) pinned to the top of the map, below the status bar and above the settings/layers button column so the two never collide structurally. Tapping it opens a modal sheet - matching the existing Schedule sheet's mechanics - listing every active announcement in full with clickable links and a "last updated" timestamp sourced from a new announcementsUpdatedAt field on MapsUiState (omitted while simulating). Also moves Schedule and Recenter to independently-anchored FABs at the bottom corners (extended/labeled for Schedule, icon-only for Recenter) now that nothing above them depends on the strip's height. Considered and dropped a per-announcement dismiss control: since it only lived in rememberSaveable state, it didn't survive a real app restart, so its only guarantee was "won't reappear this session" - too little value for the added state-tracking/pruning/UI complexity. Critical (Error) announcements were never dismissible either way and remain visible until the API resolves them. * feat(map): add bottom navigation with Map and Schedule tabs Introduces a MainTab-driven bottom NavigationBar in MapsScreen so Schedule stops being a modal sheet triggered from a map FAB and becomes its own tab. ScheduleSheet.kt is renamed to ScheduleContent.kt and its ModalBottomSheet wrapper is dropped in favor of filling the tab's content area directly. Recenter remains the map's only FAB. This is an intermediate step ahead of a larger navigation/data refactor (three-tab bar, standalone Schedule feature, ETAs tab, dev-mode fake shuttles). * feat(nav): add three-tab bottom navigation (Map, ETAs, Schedule) Wrap MapsScreen in a Scaffold with a bottom NavigationBar and switch between tabs locally instead of via Navigation3, so Map/ETAs/Schedule can share one bottom bar. Vehicle and announcement polling is now scoped to the Map tab's own composition lifetime so it stops when another tab is selected. ETAs tab is a placeholder pending commit 3. * refactor(schedule): extract Schedule into a standalone feature with caching Move ScheduleContent/ScheduleUtils out of feature/map into a new feature/schedule package, backed by its own ScheduleViewModel that caches routes and schedule the same way MapsViewModel caches routes (load-if-missing on init) and adds an explicit refresh() that drops the cache and refetches, wired to a new refresh button in the schedule header. MapsViewModel/MapsUiState no longer carry schedule state. * feat(etas): implement the ETAs tab and its stop details sheet Replace the placeholder ETAs tab with a real EtasViewModel (caches routes and polls vehicles/etas/velocities the same way MapsViewModel does for the Map tab, scoped to the tab's own composition lifetime) and a StopEtaList + StopEtaSheet UI: a route-filterable list of stops with a live eta preview per stop, and a modal sheet with the full list of vehicle etas for a tapped stop. A new EtaUtils inverts each vehicle's stopTimes map into a per-stop view and is unit tested apart from Compose/LatLng. * feat(dev-tools): add developer-mode fake moving shuttles Add a "Fake shuttles" dev menu toggle (mirrors simulate announcements) that, together with dev options, drives two vehicles continuously looping a real route's coordinates. The loop-interpolation math lives in a plain-Kotlin FakeShuttleUtils (no LatLng) so it's unit testable, and MapsViewModel publishes the result to a separate MapsUiState.fakeVehicles field that MapContent renders in its own marker loop, never merged into real vehicle data. * fix(dev-tools,etas,schedule): hardcode fake shuttles and ETAs to north/west, drop schedule reload button Fake shuttles were picking whichever route sorted first instead of a real named route, so they didn't visibly follow anything; hardcode one fake vehicle per NORTH and WEST route instead of two offset vehicles on a single arbitrarily-picked route. Apply the same NORTH/ WEST allowlist to the ETAs tab so stray or test route entries never show up as stops or filter chips. Remove the schedule page's reload button per request; ScheduleViewModel.refresh() and its cache- invalidation tests are unchanged. * docs: move installation/architecture docs into the repo, refresh README Bring the wiki's installation guide and a rewritten architecture overview into docs/, since docs living in the same repo as the code they describe are far more likely to stay in sync with it. README now leads with what the app is, its relation to Shubble and the iOS app, screenshots, and links to both docs pages. * docs: add short KDoc comments across app/, core/, data/, and feature/ Every file under edu.rpi.shuttletracker gets a brief top-of-class or top-of-file comment explaining what it's for, aimed at a new Android contributor getting oriented in the codebase. Doc-only change - no logic touched, aside from correcting two comments that had gone stale (MapsViewModel's fake-shuttle doc still described the old "pick any route" behavior; readApiResponse's doc didn't say what it actually does). * fix: gate simulated announcements on dev options, filter stray-route etas, fix schedule scroll and eta recomputation - Gate simulateAnnouncements on devOptions (like fakeShuttles already is), so relocking developer options actually stops fake banners instead of leaving them stuck on until the preference is manually flipped back off. - Filter eta vehicles by ETA_VISIBLE_ROUTES too, not just stops/filter chips, so a vehicle on a route outside NORTH/WEST can't still leak an eta chip onto a shared stop. - Fix the schedule's auto-scroll: once every departure for the day has already passed, it now lands on the last (most recent) row instead of jumping back to the top of the morning schedule. Extracted as scrollIndexFor() so it's unit tested. - Memoize EtasScreen's per-stop lookup with remember(), matching StopEtaList, instead of rebuilding it on every recomposition. * test: add androidTest coverage for schedule/etas/settings, fix two device-run failures Running these instrumented tests on a real emulator (rather than just compiling them) turned up two real bugs: - MapsScreenNavigationTest asserted the schedule subtitle as an exact full-text match, but it's only the first sentence of a longer string - onNodeWithText needs substring = true here. - ScheduleContentTest relied on ScheduleContent's auto-expand LaunchedEffect having already run by the time the assertion fired; on device it hadn't, so "Student Union" was never found. Split into its own test that explicitly expands the row instead of assuming one already is. Also defensively added composeRule.waitForIdle() wherever a test depends on a viewModelScope.launch-driven preference write or a ModalBottomSheet's open animation landing before the assertion runs (AboutScreen's tap-to-unlock, both StopEtaSheet tests) - the same class of timing issue, just not yet observed failing. * Add fake eta support for dev tools and the ETAs tab * Add ETAs home screen widget * Update announcement and button colors * Add navigation rail for wide/landscape screens * Fix Android Lint errors: LocalActivity cast, ViewModel construction in tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the FCM part of #135
Reconnect FCM and temporarily disable announcement polling. Also temporarily allow clearing notification dot through opening the app.
Notifications can now be sent via Firebase Cloud Messaging campaigns (title + text).
example:
