@livetv-app/tvguide
Version:
An Android TV Live Channels-like Electronic Programme Guide for React DOM and React Native applications.
446 lines (445 loc) • 24.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.styles = void 0;
const React = require("react");
const react_native_1 = require("react-native");
const util_1 = require("../util");
const constants_1 = require("../constants");
const context_1 = require("./context");
const types_1 = require("./types");
const channel_row_native_1 = require("./channel-row.native");
const header_native_1 = require("./header.native");
const programme_row_native_1 = require("./programme-row.native");
const debug_native_1 = require("./debug.native");
const channel_row_native_2 = require("./channel-row.native");
// @ts-ignore
const DEBUG_SHOW = 'isTesting' in react_native_1.Platform && react_native_1.Platform.isTesting ? true :
typeof process === 'object' && process.env.NODE_ENV === 'development' ? true : false;
const DEBUG_DEFAULT_TIME = null; //new Date(2020, 11, 29, 11, 0);
const DEBUG_DEFAULT_POSITION = null;
class ProgrammeGuide extends React.Component {
constructor(props) {
var _a, _b, _c, _d;
super(props);
this.state = {
channel: null,
programme: null,
time: DEBUG_DEFAULT_TIME !== null && DEBUG_DEFAULT_TIME !== void 0 ? DEBUG_DEFAULT_TIME : new Date(),
leftPosition: DEBUG_DEFAULT_POSITION !== null && DEBUG_DEFAULT_POSITION !== void 0 ? DEBUG_DEFAULT_POSITION : new Date(Math.floor((Date.now() - constants_1.MIN_DEFAULT_POSITION) / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH),
width: react_native_1.Dimensions.get('window').width,
heightDebug: 0,
heightHeader: 0,
scrollTop: null,
scrollBottom: null,
internalScrollPosition: null,
};
this.programmes_by_channel = {};
this.setSelectedChannel = (channel) => this.setState({ channel });
this.setSelectedProgramme = (programme) => this.setState({ programme });
this.setSelectedTime = (time) => this.setState({ time });
this.setLeftPosition = (leftPosition) => this.setState({ leftPosition });
this.setWidth = (width) => this.setState({ width });
this.selection = {
c: this,
get channel() { return this.c.state.channel; },
get programme() { return this.c.state.programme; },
get time() { return this.c.state.time; },
setSelectedChannel: channel => this.setState({ channel }),
setSelectedProgramme: programme => this.setState({ programme }),
setSelectedTime: time => this.setState({ time }),
};
this._handleKeypressFromDebug = (key) => {
switch (key) {
case 'select':
this.sendSelectKeypress();
break;
case 'up':
this.sendUpKeypress();
break;
case 'down':
this.sendDownKeypress();
break;
case 'left':
this.sendLeftKeypress();
break;
case 'right':
this.sendRightKeypress();
break;
}
};
const programmes = this.programmes_by_channel;
for (const programme of this.props.programmes) {
const p = (_a = programmes[programme.channel]) !== null && _a !== void 0 ? _a : (programmes[programme.channel] = []);
p.push(programme);
}
this.state.channel = (_c = (_b = props.channel) !== null && _b !== void 0 ? _b : props.channels[0]) !== null && _c !== void 0 ? _c : null;
if (this.state.channel) {
const programmes = (_d = this.programmes_by_channel[this.state.channel.id]) !== null && _d !== void 0 ? _d : [];
this.state.programme = util_1.getProgrammeAtTime(this.state.time, programmes);
}
const scrollPosition = this.calculateScrollPosition();
this.state.scrollTop = scrollPosition.scrollTop;
this.state.scrollBottom = scrollPosition.scrollBottom;
}
get leftPosition() { return this.state.leftPosition; }
componentDidUpdate(prevProps, prevState) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
if (this.props.programmes !== prevProps.programmes) {
const programmes = {};
for (const programme of this.props.programmes) {
const p = (_a = programmes[programme.channel]) !== null && _a !== void 0 ? _a : (programmes[programme.channel] = []);
p.push(programme);
}
this.programmes_by_channel = programmes;
}
if (this.props.channel && this.props.channel !== prevProps.channel) {
const time = DEBUG_DEFAULT_TIME !== null && DEBUG_DEFAULT_TIME !== void 0 ? DEBUG_DEFAULT_TIME : new Date();
const programmes = (_b = this.programmes_by_channel[this.props.channel.id]) !== null && _b !== void 0 ? _b : [];
this.setState({
channel: this.props.channel,
programme: util_1.getProgrammeAtTime(time, programmes),
time,
});
}
else if (this.state.channel && !this.props.channels.includes(this.state.channel)) {
const channel = (_c = this.props.channels[0]) !== null && _c !== void 0 ? _c : null;
this.setState({ channel });
if (channel) {
const programmes = (_d = this.programmes_by_channel[channel.id]) !== null && _d !== void 0 ? _d : [];
this.setState({
programme: util_1.getProgrammeAtTime(this.state.time, programmes),
});
}
}
if (this.props.channels !== prevProps.channels ||
this.props.scrollPosition !== prevProps.scrollPosition ||
((_e = this.props.scrollPosition) === null || _e === void 0 ? void 0 : _e.contentOffset.y) !== ((_f = prevProps.scrollPosition) === null || _f === void 0 ? void 0 : _f.contentOffset.y) ||
((_g = this.props.scrollPosition) === null || _g === void 0 ? void 0 : _g.layoutMeasurement.height) !== ((_h = prevProps.scrollPosition) === null || _h === void 0 ? void 0 : _h.layoutMeasurement.height) ||
this.props.scrollOffsetTop !== prevProps.scrollOffsetTop ||
((_j = this.state.channel) === null || _j === void 0 ? void 0 : _j.id) !== ((_k = prevState.channel) === null || _k === void 0 ? void 0 : _k.id) ||
this.state.heightDebug !== prevState.heightDebug ||
this.state.heightHeader !== prevState.heightHeader) {
this.setState(this.calculateScrollPosition());
}
if (this.props.onScroll && (this.state.scrollTop !== prevState.scrollTop ||
this.state.scrollBottom !== prevState.scrollBottom ||
this.state.leftPosition !== prevState.leftPosition ||
this.state.width !== prevState.width)) {
this.props.onScroll.call(null, {
top: this.state.scrollTop,
bottom: this.state.scrollBottom,
left: this.state.leftPosition,
right: new Date(this.state.leftPosition.getTime() + ((this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH)),
});
}
}
setChannel(channel) {
var _a;
const programmes = (_a = this.programmes_by_channel[channel.id]) !== null && _a !== void 0 ? _a : [];
this.setSelectedChannel(channel);
this.setSelectedProgramme(util_1.getProgrammeAtTime(this.selection.time, programmes));
}
setChannelUp() {
if (!this.props.channels.length)
return;
this.setChannel(util_1.getPreviousChannel(this.selection.channel, this.props.channels));
}
setChannelDown() {
if (!this.props.channels.length)
return;
this.setChannel(util_1.getNextChannel(this.selection.channel, this.props.channels));
}
setPreviousProgramme() {
var _a;
if (!this.selection.channel)
return;
const programmes = (_a = this.programmes_by_channel[this.selection.channel.id]) !== null && _a !== void 0 ? _a : [];
const time = util_1.getPreviousProgrammeTime(this.selection.time, programmes);
if (!time)
return;
this.setSelectedTime(time);
const programme = util_1.getProgrammeAtTime(time, programmes);
this.setSelectedProgramme(programme);
}
setNextProgramme() {
var _a;
if (!this.selection.channel)
return;
const programmes = (_a = this.programmes_by_channel[this.selection.channel.id]) !== null && _a !== void 0 ? _a : [];
const time = util_1.getNextProgrammeTime(this.selection.time, programmes);
if (!time)
return;
this.setSelectedTime(time);
const programme = util_1.getProgrammeAtTime(time, programmes);
this.setSelectedProgramme(programme);
}
sendSelectKeypress() {
var _a, _b, _c;
if (((_a = this.props.channel) === null || _a === void 0 ? void 0 : _a.id) !== ((_b = this.state.channel) === null || _b === void 0 ? void 0 : _b.id)) {
if (this.state.channel) {
(_c = this.props.onSelectChannel) === null || _c === void 0 ? void 0 : _c.call(null, this.state.channel);
}
return types_1.KeypressResult.OK;
}
else {
return types_1.KeypressResult.IGNORED_AS_CHANNEL_ALREADY_SELECTED;
}
}
sendLongSelectKeypress() {
var _a, _b;
if (!this.state.programme)
return types_1.KeypressResult.IGNORED_AS_NO_PROGRAMME_SELECTED;
if (!((_a = this.state.programme.actions) === null || _a === void 0 ? void 0 : _a.length))
return types_1.KeypressResult.IGNORED_AS_PROGRAMME_HAS_NO_ACTIONS;
channel_row_native_1.showProgrammeActions(this.state.programme, (_b = this.props.language) !== null && _b !== void 0 ? _b : util_1.DEFAULT_LANGUAGE);
return types_1.KeypressResult.OK;
}
sendUpKeypress(wrap = true) {
const channel = util_1.getPreviousChannel(this.state.channel, this.props.channels, wrap);
if (channel === null) {
return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
this.setChannel(channel);
// TODO: ensure the channel is visible
return types_1.KeypressResult.OK;
}
sendDownKeypress(wrap = true) {
const channel = util_1.getNextChannel(this.state.channel, this.props.channels, wrap);
if (channel === null) {
return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
this.setChannel(channel);
// TODO: ensure the channel is visible
return types_1.KeypressResult.OK;
}
setSelectedChannelAndProgramme(channel) {
var _a;
const programmes = (_a = this.programmes_by_channel[channel.id]) !== null && _a !== void 0 ? _a : [];
const programme = util_1.getProgrammeAtTime(this.state.time, programmes);
this.setState({ channel, programme });
}
setSelectedTimeAndProgramme(time, scroll = true) {
var _a;
const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH;
const rightPosition = new Date(this.state.leftPosition.getTime() + length);
const leftPosition = !scroll ? this.state.leftPosition :
this.state.leftPosition.getTime() >= (time.getTime() - constants_1.MIN_DEFAULT_POSITION) ?
new Date(Math.floor(time.getTime() / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH) :
rightPosition.getTime() <= (time.getTime() + constants_1.MIN_DEFAULT_POSITION) ?
// new Date(((Math.floor(time.getTime() / CELL_LENGTH) * CELL_LENGTH) - length) + CELL_LENGTH) :
new Date(Math.floor(((time.getTime() - length) + constants_1.CELL_LENGTH) / constants_1.CELL_LENGTH) * constants_1.CELL_LENGTH) :
this.state.leftPosition;
if (this.state.channel) {
const programmes = (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : [];
const programme = util_1.getProgrammeAtTime(time, programmes);
this.setState({ time, programme, leftPosition });
}
else {
this.setState({ time, programme: null, leftPosition });
}
}
scrollLeftRespectingBoundary(length) {
const time = new Date(this.props.leftBoundary ?
Math.max(this.props.leftBoundary.getTime(), this.state.time.getTime() - length) :
this.state.time.getTime() - length);
if (this.props.leftBoundary && this.props.leftBoundary.getTime() > time.getTime())
return false;
this.setSelectedTimeAndProgramme(time);
return true;
}
scrollRightRespectingBoundary(length) {
const time = new Date(this.props.rightBoundary ?
Math.min(this.props.rightBoundary.getTime(), this.state.time.getTime() + length) :
this.state.time.getTime() + length);
if (this.props.rightBoundary && this.props.rightBoundary.getTime() < time.getTime())
return false;
this.setSelectedTimeAndProgramme(time);
return true;
}
sendLeftKeypress() {
var _a;
const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH;
const scroll_max = length * 0.8;
if (!this.state.channel) {
return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
const time = util_1.getPreviousProgrammeTime(this.state.time, (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : []);
if (!time) {
return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
if (this.state.time.getTime() - time.getTime() > scroll_max) {
return this.scrollLeftRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
if (this.props.leftBoundary && this.props.leftBoundary.getTime() > time.getTime()) {
return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
this.setSelectedTimeAndProgramme(time);
return types_1.KeypressResult.OK;
}
sendRightKeypress() {
var _a;
const length = (this.state.width / constants_1.SCALE / constants_1.CELL_WIDTH) * constants_1.CELL_LENGTH;
const scroll_max = length * 0.8;
if (!this.state.channel) {
return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
const time = util_1.getNextProgrammeTime(this.state.time, (_a = this.programmes_by_channel[this.state.channel.id]) !== null && _a !== void 0 ? _a : []);
if (!time) {
return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
if (time.getTime() - this.state.time.getTime() > scroll_max) {
return this.scrollRightRespectingBoundary(scroll_max) ? types_1.KeypressResult.OK :
types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
if (this.props.rightBoundary && this.props.rightBoundary.getTime() < time.getTime()) {
return types_1.KeypressResult.IGNORED_DUE_TO_BOUNDARY;
}
this.setSelectedTimeAndProgramme(time);
return types_1.KeypressResult.OK;
}
shouldRenderChannelRow(total, index, channel) {
var _a, _b, _c;
const scrollPosition = this.props.internalScrolling ? (_a = this.state.internalScrollPosition) !== null && _a !== void 0 ? _a : null :
this.props.scrollPosition;
if (((_b = this.state.channel) === null || _b === void 0 ? void 0 : _b.id) === channel.id)
return true;
if (scrollPosition === undefined)
return true;
if (scrollPosition === null)
return false;
const offset_top = this.props.internalScrolling ? 0 :
((_c = this.props.scrollOffsetTop) !== null && _c !== void 0 ? _c : 0) +
(DEBUG_SHOW ? this.state.heightDebug : 0) +
this.state.heightHeader;
const position_top = offset_top + ((index * (constants_1.SCALE * 55)) - 1);
const position_bottom = position_top + scrollPosition.layoutMeasurement.height;
if (scrollPosition.contentOffset.y > position_bottom + (constants_1.SCALE * 50))
return false;
if (scrollPosition.contentOffset.y + scrollPosition.layoutMeasurement.height < position_top - (constants_1.SCALE * 50))
return false;
return true;
}
calculateRenderedRows() {
var _a, _b, _c;
const grouped = [];
const scrollPosition = (_a = (this.props.internalScrolling ?
this.state.internalScrollPosition : this.props.scrollPosition)) !== null && _a !== void 0 ? _a : { layoutMeasurement: { height: react_native_1.Dimensions.get('window').height } };
const activeindex = (_b = (this.props.channel && this.props.channels.findIndex(c => { var _a; return c.id === ((_a = this.props.channel) === null || _a === void 0 ? void 0 : _a.id); }))) !== null && _b !== void 0 ? _b : null;
const selectedindex = (_c = this.props.channels.indexOf(this.state.channel)) !== null && _c !== void 0 ? _c : null;
const maxvisiblerows = Math.ceil(scrollPosition.layoutMeasurement.height / (constants_1.SCALE * 55));
const firstrow = selectedindex - maxvisiblerows;
const lastrow = selectedindex + maxvisiblerows;
for (const i in this.props.channels) {
const index = parseInt(i);
const channel = this.props.channels[i];
if (index === activeindex || index === selectedindex || (firstrow < index && lastrow > index)) {
grouped.push({ index, channel });
}
else {
const CHANNEL_ROW_HEIGHT = (grouped.length ? 1 : 0) + 54; // scaled in render
const end = grouped[grouped.length - 1];
if (end && 'height' in end) {
end.end = index;
end.channels.push(channel);
end.height = end.height + CHANNEL_ROW_HEIGHT;
}
else
grouped.push({
start: index,
end: index,
channels: [channel],
height: CHANNEL_ROW_HEIGHT,
});
}
}
return grouped;
}
calculateScrollPosition() {
var _a, _b;
const groups = this.calculateRenderedRows();
const findFirstChannel = (c, i, arr) => 'channel' in c && (arr.length === i + 1 || (arr[i + 1] && 'height' in arr[i + 1]));
const scrollTop = (_a = groups.find(findFirstChannel)) !== null && _a !== void 0 ? _a : null;
const reversed = groups.reverse();
const scrollBottom = (_b = reversed.find(findFirstChannel)) !== null && _b !== void 0 ? _b : null;
return { scrollTop, scrollBottom };
}
render() {
const { props, selection, setWidth } = this;
const { channel, programme, leftPosition } = this.state;
const grouped = this.calculateRenderedRows();
const channels = grouped.map((r, index) => {
var _a, _b, _c;
return 'height' in r ? React.createElement(react_native_1.View, { key: index, style: [
exports.styles.channelRowPlaceholder,
index !== 0 ? exports.styles.channelRowPlaceholderNotFirstRow : null,
index === grouped.length - 1 ? exports.styles.channelRowPlaceholderLastRow : null,
{ height: constants_1.SCALE * r.height },
] }) : React.createElement(react_native_1.View, { key: r.channel.id, style: [
exports.styles.channelRowContainer,
index === grouped.length - 1 ? exports.styles.channelRowContainerLastRow : null,
] },
React.createElement(channel_row_native_1.default, { channel: r.channel, programmes: (_a = this.programmes_by_channel[r.channel.id]) !== null && _a !== void 0 ? _a : [], index: r.index, isFirstRow: r.index === 0, isLastRow: r.index === props.channels.length - 1, active: r.channel.id === ((_b = this.props.channel) === null || _b === void 0 ? void 0 : _b.id), selected: r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id), isPreviousRowSelected: ((_c = props.channels[r.index - 1]) === null || _c === void 0 ? void 0 : _c.id) === (channel === null || channel === void 0 ? void 0 : channel.id), selectedProgramme: r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id) ? programme : null, programmesRowWidth: this.state.width, showChannelLogos: this.props.showChannelLogos }),
r.channel.id === (channel === null || channel === void 0 ? void 0 : channel.id) ? programme ?
React.createElement(programme_row_native_1.default, { channel: channel, programme: programme }) :
React.createElement(programme_row_native_1.EmptyProgrammeRow, null) : null);
});
return React.createElement(react_native_1.View, { style: [exports.styles.container, this.props.style] },
DEBUG_SHOW ? this.renderDebugView() : null,
React.createElement(context_1.SelectionContext.Provider, { value: selection },
React.createElement(context_1.PositionContext.Provider, { value: leftPosition },
React.createElement(react_native_1.View, { style: [channel_row_native_2.styles.row, exports.styles.header], onLayout: e => this.setState({ heightHeader: e.nativeEvent.layout.height }) },
React.createElement(header_native_1.TimeCell, { start: leftPosition }),
React.createElement(header_native_1.TimeList, { start: leftPosition, onResize: width => setWidth(width) })),
this.props.internalScrolling ? React.createElement(react_native_1.ScrollView, { onScroll: e => this.setState({ internalScrollPosition: e.nativeEvent }), scrollEventThrottle: 20 },
channels,
React.createElement(react_native_1.View, { style: exports.styles.expandScroller })) : channels)));
}
renderDebugView() {
return React.createElement(debug_native_1.default, { channels: this.props.channels, programmes: this.props.programmes, selection: this.selection, setSelectedChannel: this.setSelectedChannel, setSelectedProgramme: this.setSelectedProgramme, setSelectedTime: this.setSelectedTime, leftPosition: this.state.leftPosition, setLeftPosition: this.setLeftPosition, width: this.state.width, scrollTop: this.state.scrollTop, scrollBottom: this.state.scrollBottom, onLayout: e => this.setState({ heightDebug: e.height }), onKeypress: this._handleKeypressFromDebug });
}
}
exports.default = ProgrammeGuide;
exports.styles = react_native_1.StyleSheet.create({
container: {
flex: 0,
maxHeight: '100%',
padding: constants_1.SCALE * 30,
paddingRight: 0,
paddingBottom: 0,
borderRadius: constants_1.SCALE * 2,
shadowColor: '#263238',
shadowOpacity: 0.8,
shadowRadius: 10,
},
header: {
backgroundColor: '#607d8bee',
borderTopLeftRadius: constants_1.SCALE * 2,
overflow: 'visible',
},
channelRowContainer: {
overflow: 'hidden',
},
channelRowContainerLastRow: {
marginBottom: 30,
},
channelRowPlaceholder: {
backgroundColor: '#37474fdd',
},
channelRowPlaceholderNotFirstRow: {
borderTopColor: '#263238dd',
borderTopWidth: 1,
},
channelRowPlaceholderLastRow: {
borderBottomLeftRadius: constants_1.SCALE * 2,
},
expandScroller: {
position: 'absolute',
height: '100%',
top: 1,
},
});