import React, { ComponentType, useCallback, useMemo } from 'react'; import { useLoadTimelineWidgetEffect, useStyles, useTimelineWidgets, useTimelineWidgetActions, } from '../../hooks'; import { StyleSheet, ActivityIndicator, View, ViewStyle, TextStyle, FlatList, ListRenderItem, } from 'react-native'; import { ComponentStyleProp, LLComponentStyleFn, WidgetMode, } from '../../types'; import { LLWidget } from './LLWidget'; import { LLWidgetSeparator, LLWidgetSeparatorProps } from './LLWidgetSeparator'; import { WidgetKind } from '@livelike/javascript'; import { LLWidgetsLoadMoreButton, LLWidgetsLoadMoreButtonProps, } from './LLWidgetsLoadMoreButton'; import { LLText } from '../LLText'; const UNSUPPORTED_TIMELINE_WIDGET_KIND = [WidgetKind.VIDEO_ALERT]; export type LLWidgetsStyles = { container: ViewStyle; errorContainer: ViewStyle; errorText: TextStyle; }; export type LLWidgetsProps = ComponentStyleProp & { programId: string; mode: WidgetMode; WidgetComponent?: typeof LLWidget; LoadingComponent?: ComponentType; ErrorComponent?: ComponentType; LoadMoreComponent?: typeof LLWidgetsLoadMoreButton; LoadMoreComponentStyles?: LLWidgetsLoadMoreButtonProps['styles']; WidgetSeparatorComponent?: ComponentType | typeof LLWidgetSeparator; WidgetSeparatorComponentStyles?: LLWidgetSeparatorProps['styles']; onLoadMore?: () => void; }; type WidgetItem = { widgetId: string; widgetKind: WidgetKind | undefined; isSeparator?: boolean; }; export function LLWidgets({ programId, mode, onLoadMore: onLoadMoreProp, WidgetComponent = LLWidget, LoadingComponent, ErrorComponent, LoadMoreComponent = LLWidgetsLoadMoreButton, WidgetSeparatorComponent = LLWidgetSeparator, WidgetSeparatorComponentStyles, LoadMoreComponentStyles, styles: stylesProp, }: LLWidgetsProps) { const styles = useStyles({ componentStylesFn: getWidgetsStyles, stylesProp, }); const { isLoading, error, onLoadMore } = useLoadTimelineWidgetEffect({ programId, mode, }); const widgets = useTimelineWidgets({ programId }); const { onWidgetInteractiveTimeout } = useTimelineWidgetActions({ programId, }); const widgetsWithSeparator = useMemo(() => { if (!widgets?.length) return []; return widgets.reduce((_widgets: WidgetItem[], widget, index) => { if (index !== 0) { _widgets.push({ widgetId: `separator-${index}`, widgetKind: undefined, isSeparator: true, }); } _widgets.push(widget); return _widgets; }, []); }, [widgets]); const onLoadMoreHandler = useCallback(() => { return onLoadMore?.().then(() => { onLoadMoreProp?.(); }); }, [onLoadMore, onLoadMoreProp]); const renderItem: ListRenderItem = useCallback( ({ item }) => { if (item.isSeparator) { return !UNSUPPORTED_TIMELINE_WIDGET_KIND.includes(item.widgetKind) ? ( ) : null; } return ( ); }, [ programId, WidgetComponent, WidgetSeparatorComponent, WidgetSeparatorComponentStyles, ] ); if (isLoading) { return LoadingComponent ? ( ) : ( ); } if (error) { return ErrorComponent ? ( ) : ( {'Unable to load widgets'} ); } const isPopupMode = mode === WidgetMode.POPUP; if (isPopupMode) { if (!widgets?.length) { return undefined; } // when in popup mode only show last older widget from widgets array, // when widget interactive timeouts or widget is dismissed, last older widget // is removed from list and then the last array item would point to second last older widget // This way we show one widget at a time in pop up mode. const { widgetId, widgetKind } = widgets[widgets.length - 1]; return ( onWidgetInteractiveTimeout(widgetId)} onInteractiveTimeout={() => onWidgetInteractiveTimeout(widgetId)} /> ); } return ( item.widgetId} ListFooterComponent={ onLoadMore ? ( ) : null } /> ); } const getWidgetsStyles: LLComponentStyleFn = ({ theme }) => StyleSheet.create({ container: { flex: 1, }, errorContainer: { display: 'flex', justifyContent: 'center', alignItems: 'center', }, errorText: { color: theme.error, }, });