tvapps-epg-mobile

1.0.9 • Public • Published

EPG Component for React Native applications.

React Native TV Guide Mobile

Default UI screenshoot

About

  • [x] EPG component
    • Material design similar to Mobile's Live Channels app
    • Supports extended data fields (channel logos, programme series/episode information, programme images, etc)
    • Supports custom actions for programmes (e.g. Open in BBC iPlayer)
    • Renderer support:
      • [x] Support React Native
      • [x] Support using a custom colour scheme
      • [x] Support callback function to get program selected
      • [x] Support slient load more programs by date
      • [x] Hooks for loading additional data when scrolling to the end of the loaded data
      • [x] Shows the current and next programme and it's start time

Install:

Note: You must be using React Native 0.60.0 or higher and import two libraries use the  react-native-fast-image and react-native-uuid.
yarn add tvapps-epg-mobile
yarn add react-native-uuid
yarn add react-native-fast-image

Data


Data is provided in formats defined. After fetch data from API, please parse them to Model with: ChannelModel,ProgramItemModel,ProgramModel:

import { processProgramsData, compareTwoDates, ChannelModel, ProgramItemModel, ProgramModel } from 'tvapps-epg-mobile';

//channel reducer: 
case ActionTypes.FETCH_CHANNELS_SUCCESS: {
            const channelList = [];
            const { channels } = payload || [];
            if (channels && channels.length > 0) {
                channels.map(item => {
                    const channelModel = new ChannelModel();
                    item.imageSrc = processChannelAttachments(item);
                    channelModel.unSerialize(item);
                    channelList.push(channelModel);
                });
            }

            return {
                ...state,
                fetchingChannels: false,
                channelList: [...state.channelList, ...channelList],
            };
        }
     
 // program reducer

case ActionTypes.FETCH_PROGRAMS_SUCCESS: {
            try {
                const { data, date } = payload || [];
                data.map(channelPrograms => {
                    const { programs } = channelPrograms;
                    programs.map(programItem => {
                        const programItemModel = new ProgramItemModel();
                        programItem.imageSrc = processProgramAttachments(programItem);
                        return programItemModel.unSerialize(programItem);
                    });
                    const programModel = new ProgramModel(channelPrograms.channelExternalId, programs);
                    return programModel;
                });
                const programsProcessed = processProgramsData(data, date);
                let statePrograms = [...state.allProgramList];
                const indexOfDateExits = statePrograms.findIndex(programsByDate => compareTwoDates(programsByDate.date, date) === true);
                if (indexOfDateExits === -1) {
                    statePrograms.push({
                        date: date,
                        data: programsProcessed,
                    });
                } else {
                    statePrograms[indexOfDateExits] = {
                        date: date,
                        data: [...statePrograms[indexOfDateExits].data, ...programsProcessed],
                    };
                }
                return {
                    ...state,
                    allProgramList: statePrograms,
                    isFetchingChannelPrograms: false
                };
            } catch (e) {
                console.log('FETCH_PROGRAMS_SUCCESS: error ', e)
            }

        }    
  1. ChannelModel:
export class Channel {
  constructor(
    imageSrc,
    id,
    externalChannelId,
    name,
    url,
    description,
    category,
    extrafields,
    number,
    isNpvrActivated,
    isCatchupActivated,
    isFavouriteActivated,
    isPurchaseActivated
  ) {
    this.imageSrc = imageSrc || '';
    this.id = id || -1;
    this.externalChannelId = externalChannelId || '';
    this.name = name || '';
    this.url = url || '';
    this.name = name || '';
    this.description = description || '';
    this.category = category || '';
    this.extrafields = extrafields || [];
    this.number = number || -1;
    this.isNpvrActivated = isNpvrActivated || false;
    this.isCatchupActivated = isCatchupActivated || false;
    this.isFavouriteActivated = isFavouriteActivated || false;
    this.isPurchaseActivated = isPurchaseActivated || false;
  }

  unSerialize(obj) {
    this.imageSrc = obj.imageSrc || '';
    this.id = obj.id || -1;
    this.externalChannelId = obj.externalChannelId || '';
    this.name = obj.name || '';
    this.url = obj.url || '';
    this.name = obj.name || '';
    this.description = obj.description || '';
    this.category = obj.category || '';
    this.extrafields = obj.extrafields || [];
    this.number = obj.number || -1;
    this.isNpvrActivated = obj.isNpvrActivated || false;
    this.isCatchupActivated = obj.isCatchupActivated || false;
    this.isFavouriteActivated = obj.isFavouriteActivated || false;
    this.isPurchaseActivated = obj.isPurchaseActivated || true;
  }
}
  1. ProgramModel, ProgramItemModel:
export class ProgramItemModel {
  constructor(
    id,
    name,
    shortName,
    seriesName,
    description,
    prName,
    startDate,
    endDate,
    startDateAdjusted,
    endDateAdjusted,
    referenceProgramId,
    flags,
    responseElementType,
    price,
    imageSrc,
    recordingStatus,
    genres,
    prLevel
  ) {
    this.id = id || -1;
    this.name = name || '';
    this.shortName = shortName || '';
    this.seriesName = seriesName || '';
    this.description = description || '';
    this.prName = prName || '';
    this.startDate = startDate || 0;
    this.endDate = endDate || 0;
    this.startDateAdjusted = startDateAdjusted || 0;
    this.endDateAdjusted = endDateAdjusted || 0;
    this.referenceProgramId = referenceProgramId || '';
    this.flags = flags || -1;
    this.responseElementType = responseElementType || '';
    this.price = price || 0;
    this.imageSrc = imageSrc || '';
    this.recordingStatus = recordingStatus || -1;
    this.genres = genres || [];
    this.prLevel = prLevel || -1;
  }

  unSerialize(obj) {
    this.id = obj.id || -1;
    this.name = obj.name || '';
    this.shortName = obj.shortName || '';
    this.seriesName = obj.seriesName || '';
    this.description = obj.description || '';
    this.prName = obj.prName || '';
    this.startDate = obj.startDate || 0;
    this.endDate = obj.endDate || 0;
    this.startDateAdjusted = obj.startDateAdjusted || 0;
    this.endDateAdjusted = obj.endDateAdjusted || 0;
    this.referenceProgramId = obj.referenceProgramId || '';
    this.flags = obj.flags || -1;
    this.responseElementType = obj.responseElementType || '';
    this.price = obj.price || 0;
    this.imageSrc = obj.imageSrc || '';
    this.recordingStatus = obj.recordingStatus || -1;
    this.genres = obj.genres || [];
    this.prLevel = obj.prLevel || -1;
  }
}

export class ProgramModel {
  constructor(channelExternalId, programs) {
    this.channelExternalId = channelExternalId || '';
    this.programs = programs || [];
  }

  unSerialize(obj) {
    this.channelExternalId = obj.channelExternalId || '';
    if (obj.programs) {
      if (obj.programs.map) {
        this.programs =
          obj.programs.map((item) => {
            const programItemModel = new ProgramItem();
            programItemModel.unSerialize(item);
            return programItemModel;
          }) || [];
      } else {
        this.programs = [];
      }
    } else {
      this.programs = [];
    }
  }
}

After you have data example:

    channel = {
        imageSrc: string,
        id: number,
        externalChannelId: string,
        name: string,
        url: string,
        description: string,
        category: string,
        extrafields: array,
        number: number,
        npvrEnabled: bool,
        isNpvrActivated: bool,
        isCatchupActivated: bool,
        catchupEnabled: bool,
        favouriteEnabled: bool,
        isFavouriteActivated: bool,
        purchaseEnabled: bool,
        isPurchaseActivated: bool,
    };
//default value
    channel =  {
        imageSrc: null,
        id: -1,
        externalChannelId: '',
        name: '',
        url: '',
        description: '',
        category: '',
        extrafields: [],
        number: -1,
        npvrEnabled: false,
        isNpvrActivated: false,
        isCatchupActivated: false,
        catchupEnabled: false,
        favouriteEnabled: false,
        isFavouriteActivated: false,
        purchaseEnabled: false,
        isPurchaseActivated: false,
    }
};
//example value
    channel =  {
        imageSrc: 'https://votvapps-ng-test.tvaas.com/RTEFacade/images/attachments/TV2.png',
        id: 1895201,
        externalChannelId: 'LuxeTV',
        name: 'Luxe TV',
        url: '',
        description: '',
        category: '',
        extrafields: [
            {
                responseElementType: "Extrafield",
                name: "static-playback",
                value: "false"
            }
        ],
        number: 12,
        npvrEnabled: false,
        isNpvrActivated: false,
        isCatchupActivated: false,
        catchupEnabled: false,
        favouriteEnabled: false,
        isFavouriteActivated: false,
        purchaseEnabled: false,
        isPurchaseActivated: false,
    }
};
const channeList =  [
    {
        ...channel
    },
    {
        ...channel   
    },
    ...
];


//PROGRAM DATA FORMART
    program = {
        id:  number,
        name:  string,
        shortName:  string,
        serisName:  string,
        description:  string,
        prName:  string,
        startDate:  number,//(timestamp)
        endDate:  number,//(timestamp)
        startDateAdjusted:  number,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
        endDateAdjusted:  number,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
        referanceProgramId:  string,
        flags:  number,
        seriesSeasion:  string,
        responseElementType:  string,
        price:  number,
        imageSrc: string,
        genres:  array,
        prLevel:  number,
    } 
// default value 
    program = {
        id:  -1,
        name:  '',
        shortName:  '',
        serisName:  '',
        description:  '',
        prName:  '',
        startDate:  0,//(timestamp)
        endDate:  0,//(timestamp)
        startDateAdjusted:  0,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
        endDateAdjusted:  0,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
        referanceProgramId:  '',
        flags:  0,
        seriesSeasion:  '',
        responseElementType:  '',
        price:  0,
        imageSrc: '',
        genres:  [],
        prLevel:  0,
    }  
//example data
 program = {
        id:  12966715,
        name:  'Los milagros de la rosa',
        shortName:  '',
        serisName:  '',
        description:  '',
        imageSrc: 'https://votvapps-ng-test.tvaas.com/RTEFacade/images/12055411.jpg',
        prName:  'APT',
        startDate:  1641769200000,//(timestamp)
        endDate:  1641776400000,//(timestamp)
        startDateAdjusted:  1641769200000,// default equal to startDate (timestamp), adjusted to fix start of day (00:00:00)
        endDateAdjusted:  1641776400000,// default equal to endDate (timestamp), adjusted to fix end of day (23:59:59)
        referanceProgramId:  '2466657917202201091800120',
        flags:  0,
        seriesSeasion:  '',
        responseElementType:  'Program',
        price:  0.0,
        genres:  [],
        prLevel:  0,
    }



const programList  = [ 
    {
        channelExternalId:'France24Fr2',
        programs: [program,...]
    },
    {
        channelExternalId:'ArteLoop',
        programs: [program,...]
    },
    {
        channelExternalId:'Arte',
        programs: [program,...]
    },
    ....
];

Data example

Usage :


Import TV Guide Mobile component with default properties below:

NOTE:

- [x] Pass props to TVGuideMobile components : channelList & programList have to length and synchronized.
- [x] ProgramModel, ChannelModel have to imageSrc property with url image is low resolution photo (200x200) recommended that good for performancer render app.
Custom Configs layout component for mobile:

You can change layout:

  • [x] timeLineHeaderHeight: Height time line header default
  • [x] programLineHeight: height of program, channel, default
  • [x] numberOfTimelineCellDisplayed: Number Time cell in time line header default 4;// equal 2 hours: 30 minutes per cell
  • [x] channelListWidth: Width list channels left of TV Guide component
  • [x] numberOfFutureDays: Number Dates Can show in the future
  • [x] numberOfPastDays: Number Dates Can show in the past

You can specify config defaults that will be applied to every request.

Config Defaults

Important pass to component properties required: .

Prop types meaning, default or output
channeList Channel list data with channel model item [channel, channel,...] (array)
programList Program list data with program model) [program,program ,...] (array)
currentDate Current date display data epg today = new Date() (Date)
numberOfFutureDays the days next from to day 3 days (number)
numberOfPastDays the past day fro today 3 days (number)
sizePerPage size data pagination fetch more data once call API 20 (number)
isLastPageOffset is lastest page fetch more data from API false or true (bool)
onDateChange Call back function when press another date header filter (date) => {} (cb func)
onProgramSelectedChange Call back func when program seleted changed ({program})=>{} (cb func)
tvGuideWidth EPG component width 30 (px)
tvGuideHeight EPG component height Device Screen height (px)
timeLineHeaderHeight time line list height (00:00-> 24:00) Device Screen width (px)
numberOfTimelineCellDisplayed number time ranger show (30 min per unit) 2 (number)
onReachingEndChannel Callback func when fetch more data channel fired) ()=>{}(cb fn)
programLineHeight height of program line height 60 (px)
channelListWidth channel list container witdth 72(px)
import React, { useCallback, useEffect, useState } from 'react';
import { View, StyleSheet, Text, PixelRatio, Image } from 'react-native';
import {
    fetchChannelsAction,
    getProgramsByChannelsAction
} from '../redux/actions';

import { TVGuideMobile, MOBILE_GUIDE_CONSTANTS, getStartDayTimestamp } from 'tvapps-epg-mobile';

import { useSelector, useDispatch } from 'react-redux';


const NUMBER_CHANNEL_DISPLAY = 10;
const SIZE_PER_PAGE = 2 * NUMBER_CHANNEL_DISPLAY;

var allProgramListSafe = [];
var today = new Date();
var channelExternalIDList = [];
var dateFilter = new Date();
var pageOffset = 1;



function HomeScreen(props) {
    const dispatch = useDispatch();
    const state = useSelector(state => state.tvGuideState);
    const {
        channelList,
        allProgramList,
        isFetchingChannelPrograms
    } = state || {};


    const [channelListState, setChannelListState] = useState([]);
    const [programListState, setProgramListState] = useState([]);
    const [currentDateDisplay, setCurrentDateDisplay] = useState(new Date());
    const [isLastPageOffset, setIsLastPageOffset] = useState(false);

    const onDateChange = useCallback(async dateValue => {
        await dispatch(getProgramsByChannelsAction([...channelExternalIDList.slice(0, SIZE_PER_PAGE)], dateValue));
        dateFilter = dateValue;
        setCurrentDateDisplay(dateValue);
        setIsLastPageOffset(false);
    }, []);

    const onReadEndChannelsPrograms = () => {
        const channelListExternalChannelId = [...channelExternalIDList.slice(pageOffset * SIZE_PER_PAGE, (pageOffset + 1) * SIZE_PER_PAGE)];
        if (channelListExternalChannelId.length > 0) {
            dispatch(getProgramsByChannelsAction(channelListExternalChannelId, currentDateDisplay));
            pageOffset++;
        }
    };

    const onProgramSelectedChange = useCallback(({ program }) => {
        console.log('onProgramSelectedChange: ', program)
    }, []);

    useEffect(() => {
        if (channelList && channelList.length > 0) {
            channelExternalIDList = channelList.map(channel => channel.externalChannelId);
            dispatch(getProgramsByChannelsAction([...channelExternalIDList.slice(0, SIZE_PER_PAGE)], today));
        }
    }, [channelList]);

    useEffect(() => {
        if (allProgramList && allProgramList.length > 0) {
            allProgramListSafe = [...allProgramList];
            const programData = allProgramListSafe.find(channelProgramList => getStartDayTimestamp(channelProgramList.date) === getStartDayTimestamp(dateFilter));
            if (programData && programData.data) {
                setProgramListState(programData.data);
                const listChannelComponent = [...channelList.slice(0, programData.data.length)];
                setChannelListState(listChannelComponent);
                if (listChannelComponent.length === channelList.length) {
                    setIsLastPageOffset(true);
                }
            }
        }
    }, [allProgramList]);

    useEffect(() => {
        dispatch(fetchChannelsAction());
    }, []);

    return (
        <View style={styles.container}>

            <TVGuideMobile
                tvGuideWidth={MOBILE_GUIDE_CONSTANTS.DEVICE_WIDTH}
                tvGuideHeight={MOBILE_GUIDE_CONSTANTS.DEVICE_HEIGHT}
                timeLineHeaderHeight={30}
                numberOfChannelsDisplayed={NUMBER_CHANNEL_DISPLAY}
                numberOfTimelineCellDisplayed={2}
                channeList={channelListState}
                programList={programListState}
                numberOfFutureDays={3}
                numberOfPastDays={2}
                currentDate={currentDateDisplay}
                onReachingEndChannel={onReadEndChannelsPrograms}
                programStylesColors={{
                    activeProgramBackgroundColor: '#463cb4',
                    currentProgramBacgroundColor: '#FFFFFF',
                    pastProgramBackgroundColor: '#463db4',
                    futureProgramBackgroundColor: '#463cb4',
                    activeProgramTextColor: '#FFFFFF',
                    currrentProgramTextColor: '#000000',
                    pastProgramTextColor: '#ffffff',
                    futureProgramTextColor: '#FFFFFF',
                    startDateProgramBackgroundColor: '#c34164',
                    startDateProgramTextColor: '#FFFFFF',
                    startDateProgramTextFontSize: 15,
                    programNameFontSize: 16
                }}
                timeLineHeaderTextFontSize={18}
                timeIndicatorStyles={{ backgroundColor: MOBILE_GUIDE_CONSTANTS.THEME_STYLES.LOADING_INDICATOR_COLOR, width: 6, borderRadius: 3 }}
                containerBackroundColor={'#0b004c'}
                programLineHeight={60}
                channelListWidth={70}
                onDateChange={onDateChange}
                onProgramSelectedChange={onProgramSelectedChange}
                sizePerPage={SIZE_PER_PAGE}
                isLastPageOffset={isLastPageOffset}
            />
        </View>

    );
}

export default React.memo(HomeScreen);

const styles = StyleSheet.create({...});
Custom Styles background color, text color...

 programStylesColors={{
                    activeProgramBackgroundColor: '#463cb4',
                    currentProgramBacgroundColor: '#FFFFFF',
                    pastProgramBackgroundColor: '#463db4',
                    futureProgramBackgroundColor: '#463cb4',
                    activeProgramTextColor: '#FFFFFF',
                    currrentProgramTextColor: '#000000',
                    pastProgramTextColor: '#ffffff',
                    futureProgramTextColor: '#FFFFFF',
                    startDateProgramBackgroundColor: '#c34164',
                    startDateProgramTextColor: '#FFFFFF',
                    startDateProgramTextFontSize: 15,
                    programNameFontSize: 16
                }}
                timeLineHeaderTextFontSize={18}
                timeIndicatorStyles={{ backgroundColor: MOBILE_GUIDE_CONSTANTS.THEME_STYLES.LOADING_INDICATOR_COLOR, width: 6, borderRadius: 3 }}
                containerBackroundColor={'#0b004c'}

Custom UI screenshoot

Project Demo import component here:

React native tvapps-epg-mobile

/tvapps-epg-mobile/

    Package Sidebar

    Install

    npm i tvapps-epg-mobile

    Weekly Downloads

    3

    Version

    1.0.9

    License

    ISC

    Unpacked Size

    2.66 MB

    Total Files

    47

    Last publish

    Collaborators

    • nexlesoft