Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fsengagement): add gridwall and product carousel #816

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added packages/fsengagement/assets/images/dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/fsengagement/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@types/react-native-snap-carousel": "^3.7.3",
"@types/react-native-video": "^2.0.8",
"lodash-es": "^4.17.10",
"moment": "^2.22.2",
"prop-types": "^15.6.2",
"react": "16.8.3",
"react-native": "0.59.10",
Expand Down
54 changes: 40 additions & 14 deletions packages/fsengagement/src/EngagementComp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ViewStyle
} from 'react-native';
import { EngagementService } from './EngagementService';
import TabbedStory from './inboxblocks/TabbedStory';
import PropTypes from 'prop-types';
import { Navigation } from 'react-native-navigation';
import {
Expand Down Expand Up @@ -58,6 +59,10 @@ const styles = StyleSheet.create({
top: 70,
left: 20
},
editorial: {
marginTop: 0,
backgroundColor: 'transparent'
},
pageNum: {
color: '#ffffff',
fontWeight: '500'
Expand Down Expand Up @@ -110,6 +115,9 @@ export interface EngagementScreenProps extends ScreenProps, EmitterProps {
autoplay?: boolean;
autoplayDelay?: number;
autoplayInterval?: number;
storyType?: string;
tabbedItems?: any[];
lastUpdate?: number;
containerStyle?: StyleProp<ViewStyle>;
}
export interface EngagementState {
Expand Down Expand Up @@ -285,19 +293,20 @@ export default function(
}

renderBlocks(): JSX.Element {
const { json, json: { empty } } = this.props;
const { json } = this.props;
const empty: any = json && json.empty || {};
return (
<Fragment>
{(json.private_blocks || []).map(this.renderBlock)}
{empty && !(json.private_blocks && json.private_blocks.length) &&
{(json && json.private_blocks || []).map(this.renderBlock)}
{empty && !(json && json.private_blocks && json.private_blocks.length) &&
<Text style={[styles.emptyMessage, empty.textStyle]}>
{empty.message || 'No content found.'}</Text>}
</Fragment>
);
}

renderStoryGradient(): JSX.Element {
const { json: { storyGradient } } = this.props;
const { json: { storyGradient, tabbedItems } } = this.props;
const { scrollY } = this.state;
const empty: any = this.props.json.empty || {};
const {
Expand All @@ -309,6 +318,14 @@ export default function(
outputRange: [0, 1],
extrapolate: 'clamp'
});
if (tabbedItems && tabbedItems.length) {
return (
<TabbedStory
items={tabbedItems}
componentId={this.props.componentId}
/>
);
}
return (
<Fragment>
<FlatList
Expand Down Expand Up @@ -346,21 +363,23 @@ export default function(
}
// tslint:disable-next-line:cyclomatic-complexity
renderScrollView(): JSX.Element {
const { json, json: { storyGradient } } = this.props;
const empty: any = this.props.json.empty || {};
const { json } = this.props;
const storyGradient = json && json.storyGradient;
const tabbedItems = json && json.tabbedItems;
const empty: any = json && json.empty || {};

if (this.props.renderType && this.props.renderType === 'carousel') {
const autoplay = this.props.autoplay || false;
const autoplayDelay = this.props.autoplayDelay || 1000;
const autoplayInterval = this.props.autoplayInterval || 3000;
return (
<Fragment>
{empty && !(json.private_blocks && json.private_blocks.length) &&
<Text style={[styles.emptyMessage, empty.textStyle]}>
{empty.message || 'No content found.'}</Text>}
{empty && !(json && json.private_blocks && json.private_blocks.length) &&
<Text style={[styles.emptyMessage, empty && empty.textStyle]}>
{empty && empty.message || 'No content found.'}</Text>}

{this.state.showCarousel && <Carousel
data={json.private_blocks || []}
data={json && json.private_blocks || []}
layout={'default'}
autoplay={autoplay}
autoplayDelay={autoplayDelay}
Expand All @@ -374,7 +393,7 @@ export default function(
useScrollView={Platform.OS === 'ios' ? true : false}
/>}
{!this.state.showCarousel && <ActivityIndicator style={styles.growAndCenter} />}
{(json.private_blocks && json.private_blocks.length) &&
{(json && json.private_blocks && json.private_blocks.length > 0) &&
<View style={[styles.pageCounter, json.pageCounterStyle]}>
<Text
style={[styles.pageNum, json.pageNumberStyle]}
Expand All @@ -393,15 +412,22 @@ export default function(
);
} else if (this.props.backButton && storyGradient && storyGradient.enabled) {
return this.renderStoryGradient();
} else if (tabbedItems && tabbedItems.length) {
return (
<TabbedStory
items={tabbedItems}
componentId={this.props.componentId}
/>
);
}
return (
<FlatList
data={this.props.json.private_blocks || []}
data={this.props.json && this.props.json.private_blocks || []}
keyExtractor={this.dataKeyExtractor}
renderItem={this.renderBlockItem}
ListEmptyComponent={(
<Text style={[styles.emptyMessage, empty.textStyle]}>
{empty.message || 'No content found.'}</Text>
<Text style={[styles.emptyMessage, empty && empty.textStyle]}>
{empty && empty.message || 'No content found.'}</Text>
)}
refreshControl={
this.props.refreshControl && <RefreshControl
Expand Down
55 changes: 55 additions & 0 deletions packages/fsengagement/src/EngagementService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import FCM, { FCMEvent } from 'react-native-fcm';
import AsyncStorage from '@react-native-community/async-storage';
import FSNetwork from '@brandingbrand/fsnetwork';
import DeviceInfo from 'react-native-device-info';
import moment from 'moment';

import {
EngagementMessage,
EngagementProfile,
Expand Down Expand Up @@ -237,6 +239,59 @@ export class EngagementService {
});
}


async getInboxMessages(attributes?: Attribute[]): Promise<EngagementMessage[]> {
// check we have a user profile
if (!this.profileId) {
throw new Error('Profile not loaded.');
}

// cache
if (this.messages.length) {
if (+new Date() - this.messageCache < this.cacheTTL) {

return Promise.resolve(this.messages);
}
}

const lastEngagementFetch = await AsyncStorage.getItem('LAST_ENGAGEMENT_FETCH');
return this.networkClient.post(`/PublishedMessages/getInboxForProfile/${this.profileId}`,
JSON.stringify(attributes))
.then((r: any) => r.data)
.then((list: any) => list.map((data: any) => {
return {
id: data.id,
published: new Date(data.published),
isNew: lastEngagementFetch ?
moment.utc(data.published).valueOf() > parseInt(lastEngagementFetch, 10) : false,
message: JSON.parse(data.message),
title: data.title,
inbox: data.inbox,
attributes: data.attributes
};
}))
.then((messages: EngagementMessage[]) => {
this.messages = messages;
this.messageCache = +new Date();
AsyncStorage.setItem('LAST_ENGAGEMENT_FETCH', moment.utc(new Date()).valueOf().toString())
.catch();
return messages;
})
.catch(async (e: any) => {
console.log('Unable to fetch inbox messages', e);

let ret: EngagementMessage[] = [];

// respond with stale cache if we have it
if (this.messages.length) {
ret = this.messages;
}

return Promise.resolve(ret);
});
}


async onNotification(notif: EngagmentNotification): Promise<void> {
console.log('onNotification', notif);

Expand Down
94 changes: 94 additions & 0 deletions packages/fsengagement/src/carousel/RenderItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* tslint:disable */
import React, { Component } from 'react';
import { Dimensions, Image, Text, TouchableOpacity, View } from 'react-native';
import { ParallaxImage } from 'react-native-snap-carousel';
import styles from './SliderEntry.style';

export interface RenderItemProps {
data?: any;
parallax?: any;
parallaxProps?: any;
even?: boolean;
horizPadding: number,
itemWidth: number
}

const { height: viewportHeight } = Dimensions.get('window');

export default class RenderItem extends Component<RenderItemProps> {
get image(): any {
const { data: { source }, parallax, parallaxProps, even } = this.props;

return parallax ? (
<ParallaxImage
source={source}
containerStyle={[styles.imageContainer, even ? styles.imageContainerEven : {}]}
style={styles.image}
parallaxFactor={0.35}
showSpinner={true}
spinnerColor={even ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.25)'}
{...parallaxProps}
/>
) : (
<Image
source={source}
style={styles.image}
/>
);
}
render() {
const {
data: {
ratio,
title,
subtitle
},
even,
itemWidth,
horizPadding = 0
} = this.props;

let itemStyle: any = {};
if (ratio && itemWidth) {
itemStyle = {
width: itemWidth,
height: itemWidth / parseFloat(ratio),
paddingHorizontal: horizPadding
};
} else {
itemStyle = {
width: itemWidth,
height: viewportHeight * .36,
paddingHorizontal: horizPadding
};
}
const uppercaseTitle = title ? (
<Text
style={[styles.title, even ? styles.titleEven : {}]}
numberOfLines={2}
>
{title.toUpperCase()}
</Text>
) : false;

return (
<TouchableOpacity
activeOpacity={1}
style={itemStyle}
>
<View style={[styles.imageContainerNoCard, even ? {} : {}]}>
{this.image}
</View>
{title && <View style={[styles.textContainer, even ? styles.textContainerEven : {}]}>
{uppercaseTitle}
<Text
style={styles.subtitle}
numberOfLines={2}
>
{subtitle}
</Text>
</View>}
</TouchableOpacity>
);
}
}
89 changes: 89 additions & 0 deletions packages/fsengagement/src/carousel/RenderProduct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* tslint:disable */
import React, { Component } from 'react';
import {
Dimensions, Image, Text, StyleProp, TextStyle, TouchableOpacity, View
} from 'react-native';
import styles from './SliderEntry.style';

export interface RenderProductProps {
data?: any;
horizPadding?: number;
spaceBetweenHorizontal?: number;
spaceBetweenVertical?: number;
itemWidth: number;
even?: boolean;
priceStyle?: StyleProp<TextStyle>;
titleStyle?: StyleProp<TextStyle>;
}

const { height: viewportHeight } = Dimensions.get('window');

export default class RenderProduct extends Component<RenderProductProps> {
get image(): any {
const { data } = this.props;
return (
<Image
source={{uri: data.image.url}}
style={styles.image}
/>
);
}
onImagePress = (): void => {

}
render() {
const {
data: {
name,
price
},
even = false,
itemWidth,
horizPadding = 0,
spaceBetweenHorizontal = 0,
spaceBetweenVertical = 0
} = this.props;

const ratio = '.75';
let itemStyle: any = {};
if (ratio && itemWidth) {
itemStyle = {
width: even ? itemWidth - 1 : itemWidth,
height: (itemWidth / parseFloat(ratio)) + 65,
paddingHorizontal: horizPadding,
marginLeft: even ? spaceBetweenHorizontal : 0,
marginBottom: spaceBetweenVertical
};
} else {
itemStyle = {
width: itemWidth,
height: viewportHeight * .36,
paddingHorizontal: horizPadding
};
}
return (
<TouchableOpacity
activeOpacity={1}
style={itemStyle}
onPress={this.onImagePress}
>
<View style={styles.imageContainerNoCard}>
{this.image}
</View>
{name && <View style={styles.textContainer}>
<Text
style={[styles.title, this.props.titleStyle]}
numberOfLines={2}
>
{name}
</Text>
<Text
style={[styles.subtitle, this.props.priceStyle]}
>
{price.formattedValue}
</Text>
</View>}
</TouchableOpacity>
);
}
}
Loading