Files
calendar/companion/utils/widgetStorage.android.ts
T
Peer RichelsenGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>DhairyashilDhairyashil ShindeVolnei Munhoz
8a7cf24f90 feat(companion): add upcoming bookings widget for iOS and Android (#27199)
* feat(companion): add upcoming bookings widget for iOS and Android

- Add iOS widget using @bacons/apple-targets with SwiftUI
- Add Android widget using react-native-android-widget
- Configure App Groups for iOS data sharing between app and widget
- Create widget sync hook to update widget data when bookings change
- Widget displays up to 5 upcoming bookings with title, time, and attendee
- Widget refreshes every 15 minutes and when app goes to background

Co-Authored-By: peer@cal.com <peer@cal.com>

* fix(companion): refactor useWidgetSync to avoid React Compiler limitation

Move conditional logic outside of try/catch block to fix React Compiler
error: 'Support value blocks (conditional, logical, optional chaining,
etc) within a try/catch statement'

Co-Authored-By: peer@cal.com <peer@cal.com>

* fix(companion): fix widget data sync issues

- Fix Swift widget to read data as JSON string (format used by react-native-shared-group-preferences)
- Add fallback to read as raw Data for compatibility
- Fix query key mismatch by checking multiple common filter combinations
- Ensure widget finds cached bookings regardless of which filter the app used

Co-Authored-By: peer@cal.com <peer@cal.com>

* fix(companion): use ExtensionStorage from @bacons/apple-targets for widget data

- Replace react-native-shared-group-preferences with ExtensionStorage
- ExtensionStorage properly stores data in iOS App Groups
- Add ExtensionStorage.reloadWidget() call to trigger widget refresh
- This ensures the widget picks up new data immediately

Co-Authored-By: peer@cal.com <peer@cal.com>

* feat(companion): add upcoming bookings widget for ios - all shapes (#27564)

* ios working widget

* fix(companion): remove sensitive data logging from widget storage

Remove console.log statements that were logging full widgetData payload
and formatted widget bookings containing attendee names and booking
details. This addresses security concerns identified by Cubic AI review
(confidence 9/10).

Co-Authored-By: unknown <>

* dark mode

* new look

* fix deeplink

* fix biome lint

* fix(companion): remove booking titles from console logs to avoid PII exposure

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* lock the versions and dev mode logging

* remove dynamic imports

* feat(companion): upcoming bookings widget android (#27689)

* companion-upcoming-bookings-widget-android

* fix(companion): remove stale relative countdowns and fix past-time clamping in Android widget

- Remove relative countdown text (In 15m, In 1h, Now) that becomes stale
  due to Android widget 30+ min update interval (Cubic violation 2)
- Remove Math.max(0, minutes) clamping that caused ended meetings to
  display as 'Now' indefinitely (Cubic violation 1)
- Simplify accent color logic to use startTimeISO directly
- Keep absolute time display (date + startTime) which remains accurate

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Dhairyashil <dhairyashil10101010@gmail.com>
Co-authored-by: Dhairyashil Shinde <93669429+dhairyashiil@users.noreply.github.com>
Co-authored-by: Volnei Munhoz <volnei@cal.com>
2026-02-06 12:32:46 +00:00

54 lines
1.6 KiB
TypeScript

import AsyncStorage from "@react-native-async-storage/async-storage";
import { requestWidgetUpdate } from "react-native-android-widget";
import { UpcomingBookingsWidget } from "@/widgets/UpcomingBookingsWidget";
import {
type BookingInput,
type WidgetData,
ANDROID_WIDGET_STORAGE_KEY,
transformBookingsToWidgetData,
} from "./widgetStorage.shared";
async function updateAndroidWidget(widgetData: WidgetData): Promise<void> {
await AsyncStorage.setItem(ANDROID_WIDGET_STORAGE_KEY, JSON.stringify(widgetData));
try {
await requestWidgetUpdate({
widgetName: "UpcomingBookingsWidget",
renderWidget: () => UpcomingBookingsWidget({ bookings: widgetData.bookings }),
widgetNotFound: () => {
console.warn("Widget not found on home screen");
},
});
} catch (error) {
console.warn("Failed to request Android widget update:", error);
}
}
export async function updateWidgetBookings(bookings: BookingInput[]): Promise<void> {
if (__DEV__) {
console.log("[Widget Debug] updateWidgetBookings called with", bookings.length, "bookings");
}
try {
const widgetData = transformBookingsToWidgetData(bookings);
await updateAndroidWidget(widgetData);
} catch (error) {
console.warn("Failed to update widget bookings:", error);
}
}
export async function clearWidgetBookings(): Promise<void> {
try {
const emptyData: WidgetData = {
bookings: [],
lastUpdated: new Date().toISOString(),
};
await updateAndroidWidget(emptyData);
} catch (error) {
console.warn("Failed to clear widget bookings:", error);
}
}
// Re-export shared utilities
export * from "./widgetStorage.shared";