@quintype/native-components 2.21.0-beta.2 → 2.21.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.20.32](https://github.com/quintype/native-components/compare/v2.20.31...v2.20.32) (2024-06-05)
6
+
7
+ ### [2.20.31](https://github.com/quintype/native-components/compare/v2.20.30...v2.20.31) (2024-06-04)
8
+
9
+ ### [2.20.30](https://github.com/quintype/native-components/compare/v2.20.29...v2.20.30) (2024-06-04)
10
+
11
+ ### [2.20.29](https://github.com/quintype/native-components/compare/v2.20.28...v2.20.29) (2024-06-04)
12
+
13
+ ### [2.20.28](https://github.com/quintype/native-components/compare/v2.20.27...v2.20.28) (2024-05-17)
14
+
15
+ ### [2.20.27](https://github.com/quintype/native-components/compare/v2.20.26...v2.20.27) (2024-05-17)
16
+
17
+ ### [2.20.26](https://github.com/quintype/native-components/compare/v2.20.25...v2.20.26) (2024-05-17)
18
+
5
19
  ### [2.20.25](https://github.com/quintype/native-components/compare/v2.20.24...v2.20.25) (2024-03-14)
6
20
 
7
21
 
package/README.md CHANGED
@@ -4,7 +4,26 @@
4
4
 
5
5
  `$ npm install @quintype/native-components --save`
6
6
 
7
- ### Linking
7
+ ## Creating and installing a beta version
8
+
9
+ ### Publishing a version with beta tag:
10
+ 1. Add, commit and push beta changes on a new branch.
11
+ 2. Run `npm i --legacy-peer-deps` (Using **node@16.19.0**, **npm@8.19.3**)
12
+ 3. Run `npm cache clean --force` in case the above command fails.
13
+ 4. Create a tag by running `npm version 2.21.0-beta.0` (add **\`-beta.*iteration*\`** (where ***iteration*** can just be incremented (+1) for subsequent beta-versions on the same development branch)
14
+ 5. Publish the tag by running `npm publish --tag beta`
15
+ 6. Make sure to commit and push the new beta versioning changes.
16
+
17
+ ### Installing a version with beta tag on host app:
18
+ npm install @quintype/native-components@beta
19
+
20
+ ### Note:
21
+ - `prePublishOnly` script that was in use before is incompatible with recent node/npm versions. Hence `standard-version-release.sh` is not used.
22
+ - Revert `"version"` changes from `package.json` before creating PR (for merging to `master`).
23
+ - Access all versions [here](https://www.npmjs.com/package/@quintype/native-components?activeTab=versions).
24
+
25
+
26
+ ## Linking
8
27
 
9
28
  This library has both js only as well as native components. Please check the instructions for the respective components for more details.
10
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quintype/native-components",
3
- "version": "2.21.0-beta.2",
3
+ "version": "2.21.0-beta.3",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -20,7 +20,8 @@
20
20
  "react-native-image-pan-zoom": "^2.1.12",
21
21
  "react-native-lightbox": "0.8.1",
22
22
  "react-native-render-html": "^4.2.3",
23
- "react-native-share": "^8.1.0"
23
+ "react-native-share": "^8.1.0",
24
+ "react-native-star-rating-widget": "^1.7.3"
24
25
  },
25
26
  "peerDependencies": {
26
27
  "@react-navigation/native": ">=6.1.17",
@@ -31,7 +32,8 @@
31
32
  "react-native-pdf": ">=6.7.4",
32
33
  "react-native-blob-util": ">=6.7.4",
33
34
  "react-native-webview": ">=11.0.0",
34
- "react-native-vector-icons": "^10.0.0"
35
+ "react-native-vector-icons": "^10.0.0",
36
+ "react-native-linear-gradient": "^2.8.3"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@babel/core": "^7.11.1",
@@ -1,18 +1,10 @@
1
- import React from "react";
2
- import Icon from "react-native-vector-icons/Ionicons";
3
- import PropTypes from "prop-types";
1
+ import React, { useContext } from "react";
2
+ import Icon from "react-native-vector-icons/FontAwesome";
3
+ import { AppTheme } from "../../utils";
4
4
 
5
- const DEFAULT_FALLBACK_SIZE = 25;
5
+ export const FallbackIcon = () => {
6
+ const { theme } = useContext(AppTheme);
7
+ const { COLORS } = theme;
6
8
 
7
- export const FallbackIcon = ({ size = DEFAULT_FALLBACK_SIZE, color }) => (
8
- <Icon
9
- name="images"
10
- size={size}
11
- color={color}
12
- />
13
- );
14
-
15
- FallbackIcon.propTypes = {
16
- size: PropTypes.number,
17
- color: PropTypes.string,
9
+ return <Icon name="image" size={25} color={COLORS.MONO4} />;
18
10
  };
@@ -0,0 +1,26 @@
1
+ import { TouchableOpacity, View, I18nManager, StyleSheet, Linking } from "react-native";
2
+ import HTML from "react-native-render-html";
3
+ import React from "react";
4
+
5
+ const CustomHtmlParser = ({ text, textStyle={}, containerStyle={} }) => {
6
+ const textStyles = StyleSheet.flatten([
7
+ textStyle,
8
+ {
9
+ writingDirection: I18nManager.isRTL ? "rtl" : "ltr",
10
+ },
11
+ ]);
12
+
13
+ return (
14
+ <HTML
15
+ html={text}
16
+ key={Math.random()}
17
+ baseFontStyle={textStyles}
18
+ onLinkPress={(evt, href) => {
19
+ Linking.openURL(href);
20
+ }}
21
+ containerStyle={containerStyle}
22
+ />
23
+ );
24
+ };
25
+
26
+ export default CustomHtmlParser;
@@ -0,0 +1,50 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useContext } from 'react';
3
+ import { View } from 'react-native';
4
+ import { StarRatingDisplay } from 'react-native-star-rating-widget';
5
+ import { Text } from '../Text';
6
+ import { ratingStyles } from './styles';
7
+ import { AppTheme } from '../../utils';
8
+
9
+ export const RatingLayout = ({ reviewTitle, ratingValue, ratingLabel }) => {
10
+ const { theme } = useContext(AppTheme);
11
+ const {
12
+ COLORS, FONT_FAMILY, FONT_SIZE,
13
+ } = theme;
14
+ const styles = ratingStyles(COLORS, FONT_SIZE, FONT_FAMILY);
15
+
16
+ return (
17
+ <View style={styles.container}>
18
+ {reviewTitle && (
19
+ <Text
20
+ style={styles.reviewTitle}
21
+ primary
22
+ >
23
+ {reviewTitle}
24
+ </Text>
25
+ )}
26
+ {ratingValue && (
27
+ <View style={styles.child}>
28
+ <Text
29
+ style={styles.ratingLabel}
30
+ >
31
+ {`${ratingLabel}/5`}
32
+ </Text>
33
+ <StarRatingDisplay
34
+ rating={ratingValue}
35
+ starSize={FONT_SIZE.title}
36
+ color={COLORS.REVIEW_STAR_COLOR ?? '#F5A623'}
37
+ style={styles.starContiner}
38
+ starStyle={styles.starStyle}
39
+ />
40
+ </View>
41
+ )}
42
+ </View>
43
+ );
44
+ };
45
+
46
+ RatingLayout.propTypes = {
47
+ reviewTitle: PropTypes.string.isRequired,
48
+ ratingValue: PropTypes.number.isRequired,
49
+ ratingLabel: PropTypes.string.isRequired,
50
+ };
@@ -0,0 +1,21 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export const ratingStyles = (COLORS, FONT_SIZE, FONT_FAMILY) => StyleSheet.create({
4
+ container: { marginLeft: 10, marginTop: 10 },
5
+ reviewTitle: {
6
+ fontSize: FONT_SIZE.h2,
7
+ lineHeight: 24,
8
+ fontFamily: FONT_FAMILY.primary,
9
+ color: COLORS.BRAND_BLACK,
10
+ marginBottom: 7,
11
+ },
12
+ child: { display: 'flex', flexDirection: 'row', alignItems: 'center' },
13
+ ratingLabel: {
14
+ fontSize: FONT_SIZE.h2,
15
+ lineHeight: 24,
16
+ fontFamily: FONT_FAMILY.secondary,
17
+ color: COLORS.BRAND_BLACK,
18
+ },
19
+ starContainer: { marginLeft: 10 },
20
+ starStyle: { marginHorizontal: 0 },
21
+ });
@@ -33,7 +33,7 @@ const ResponsiveImageBase = (props) => {
33
33
 
34
34
  const placeholderStyle = {
35
35
  ...StyleSheet.absoluteFillObject,
36
- backgroundColor: CustomFallBackBackground || COLORS.MONO6,
36
+ backgroundColor: (CustomFallBackIcon && CustomFallBackBackground) ?? COLORS.MONO6,
37
37
  justifyContent: 'center',
38
38
  alignItems: 'center',
39
39
  };
@@ -13,6 +13,8 @@ import { ClockIcon } from '../../Icons/ClockIcon';
13
13
  import { storyStyles } from './styles';
14
14
  import { isStoryFree, mutateDataBeforeAccess, getFirstVideoElement } from '../../utils/story';
15
15
  import { isMiddleIndexOfArray } from '../../utils/arrayUtils';
16
+ import LinearGradient from "react-native-linear-gradient";
17
+ import { hexToRgb } from '@quintype/native-components/src/utils/colorUtils';
16
18
 
17
19
  const getLiveBlogTimeStamp = (card, DATE_FORMAT, share, styles, locale) => {
18
20
  const slug = `?cardId=${card.id}`;
@@ -76,7 +78,7 @@ const getStoryCards = (
76
78
  getAd,
77
79
  locale,
78
80
  ) => cards.map((card, index, source) => (
79
- <View key={card?.id}>
81
+ <View key={card?.id}>
80
82
  {isLiveBlog
81
83
  && getLiveBlogTimeStamp(card, DATE_FORMAT, share, styles, locale)}
82
84
 
@@ -106,7 +108,6 @@ const getStoryCards = (
106
108
  {/* ^ In the case of a story having just 1 card,
107
109
  requesting mid-content-Ad if there are > 1 story-elements in that card
108
110
  (> 2 story-elements in case of video story since the 1st story-element gets rendered in header). */}
109
-
110
111
  <StoryContent
111
112
  testID={contentTestID}
112
113
  key={storyElement?.id}
@@ -143,15 +144,16 @@ export const Story = ({
143
144
  storyHasAccess = 'loading',
144
145
  currentLayout,
145
146
  getAd,
147
+ linkedStories,
148
+ showWall,
149
+ userObjectLength,
150
+ numberOfVisibleStoryCard
146
151
  }) => {
147
152
  const { theme } = useContext(AppTheme);
148
- const { COLORS, FONT_SIZE, locale } = theme;
153
+ const { COLORS, FONT_SIZE, locale, DARK_MODE } = theme;
149
154
  const styles = storyStyles(COLORS, FONT_SIZE);
155
+ const cards = story?.cards ?? [];
150
156
 
151
- const {
152
- cards = [],
153
- 'linked-stories': linkedStories,
154
- } = mutateDataBeforeAccess(story, storyHasAccess);
155
157
 
156
158
  const firstVideoElement = story['story-template'] === STORY_TYPES.VIDEO_STORY
157
159
  ? getFirstVideoElement(cards)
@@ -184,8 +186,28 @@ export const Story = ({
184
186
  checkStoryAccess(story);
185
187
  }, []);
186
188
 
189
+ useEffect(()=>{
190
+ if(userObjectLength){
191
+ checkStoryAccess(story);
192
+ }
193
+ },[userObjectLength])
194
+
195
+
196
+ const GradientView = () => {
197
+ return (
198
+ <LinearGradient
199
+ colors={[
200
+ DARK_MODE ? hexToRgb("#252525", 0) : hexToRgb(COLORS.BRAND_WHITE, 0),
201
+ DARK_MODE ? hexToRgb("#252525", 1) : hexToRgb(COLORS.BRAND_WHITE, 1),
202
+ ]}
203
+ style={{ height: 200}}
204
+ ></LinearGradient>
205
+ );
206
+ };
207
+
187
208
  return (
188
209
  <>
210
+ <View>
189
211
  <StoryHeader
190
212
  firstVideoElement={firstVideoElement}
191
213
  testID={headerTestID}
@@ -195,6 +217,8 @@ export const Story = ({
195
217
  onSectionPress={onSectionPress}
196
218
  />
197
219
 
220
+
221
+
198
222
  {getAd()}
199
223
 
200
224
  {getStoryCards(
@@ -218,7 +242,13 @@ export const Story = ({
218
242
  locale,
219
243
  )}
220
244
 
221
- {accessComponent(story, storyHasAccess)}
245
+ {(!(isStoryFree(story) === 0 || storyHasAccess === "granted") && showWall) && <View style={styles.overlay} >
246
+ <GradientView />
247
+ </View>
248
+ }
249
+ </View>
250
+ {accessComponent(story, storyHasAccess, showWall)}
251
+
222
252
 
223
253
  {shouldShowComments()}
224
254
 
@@ -37,4 +37,7 @@ export const storyStyles = (COLORS, FONT_SIZE) => StyleSheet.create({
37
37
  color: COLORS.BRAND_1,
38
38
  fontSize: FONT_SIZE.title,
39
39
  },
40
+ overlay:{
41
+ marginTop:-220
42
+ }
40
43
  });
@@ -1,7 +1,7 @@
1
1
  import get from 'lodash/get';
2
2
  import PropTypes from 'prop-types';
3
3
  import React, { useContext } from 'react';
4
- import { TouchableOpacity, View } from 'react-native';
4
+ import { TouchableOpacity, View, I18nManager,StyleSheet } from 'react-native';
5
5
  import {
6
6
  getImageMetadata,
7
7
  getScreenPercentageWidth,
@@ -11,13 +11,16 @@ import {
11
11
  } from '../../utils';
12
12
  import { STORY_TYPES } from '../../utils/story-types';
13
13
  import {
14
- AuthorRow, ResponsiveImage, StoryTitle, Text,
14
+ AuthorRow, ResponsiveImage, StoryText, StoryTitle, Text,
15
15
  } from '../index';
16
16
  import { YouTubePlayer } from '../YouTubePlayer';
17
17
  import { storyHeaderStyles } from './styles';
18
18
  import { COMP_CONTENT_CONSTANTS } from '../../constants/component-constants/content-constants/constants';
19
19
 
20
20
  import { DailyMotionPlayer } from '../DailyMotionPlayer';
21
+ import { RatingLayout } from '../Rating';
22
+ import CustomHtmlParser from './../Html/index';
23
+
21
24
  const getHeroImage = (cdn, story) => {
22
25
  const imageSlug = story['hero-image-s3-key'];
23
26
  if (!imageSlug) return null;
@@ -33,7 +36,8 @@ const getHeroImage = (cdn, story) => {
33
36
  };
34
37
 
35
38
  const getHeroComponent = (props) => {
36
- const { story = {}, firstVideoElement, cdn } = props;
39
+ const { story = {},firstVideoElement, cdn } = props;
40
+
37
41
  switch (story['story-template']) {
38
42
  case STORY_TYPES.VIDEO_STORY: {
39
43
  if (!firstVideoElement) return null;
@@ -60,12 +64,12 @@ const getHeroComponent = (props) => {
60
64
  };
61
65
 
62
66
  export const StoryHeader = (props) => {
63
- const { story } = props;
67
+ const { story, firstVideoElement } = props;
64
68
  const sectionData = get(story, ['sections', 0], {});
65
69
  const { theme } = useContext(AppTheme);
66
70
 
67
71
  const {
68
- COLORS, locale, FONT_FAMILY, DATE_TIME_FORMAT, premiumIcon, FONT_SIZE
72
+ COLORS, locale, FONT_FAMILY, DATE_TIME_FORMAT, FONT_SIZE
69
73
  } = theme;
70
74
 
71
75
  const DATE_FORMAT = `${DATE_TIME_FORMAT.dateFormat} ${DATE_TIME_FORMAT.timeFormat}`;
@@ -73,7 +77,8 @@ export const StoryHeader = (props) => {
73
77
  const caption = story['hero-image-caption'];
74
78
  const attribution = story['hero-image-attribution'];
75
79
  const isPremiumStory = story['access'] === 'subscription';
76
-
80
+ const reviewTitle = story['metadata'] && story['metadata']['review-title'];
81
+ const reviewData = story['metadata'] && story['metadata']['review-rating'];
77
82
 
78
83
  const showAttribution = () => {
79
84
  if (!caption && !attribution) {
@@ -81,8 +86,8 @@ export const StoryHeader = (props) => {
81
86
  }
82
87
  return (
83
88
  <View style={styles.captionContainerStyle}>
84
- <Text style={styles.captionStyle}>{stripHTML(caption)}</Text>
85
- <Text style={styles.attributionStyle}>{stripHTML(attribution)}</Text>
89
+ {caption && !firstVideoElement && <CustomHtmlParser text={caption} textStyle={styles.captionStyle}/>}
90
+ {attribution && !firstVideoElement && <CustomHtmlParser text={attribution} textStyle={styles.attributionStyle}/>}
86
91
  </View>
87
92
  );
88
93
  };
@@ -128,6 +133,7 @@ export const StoryHeader = (props) => {
128
133
  cdn={props.cdn}
129
134
  readtime={story['read-time']}
130
135
  />
136
+ {reviewTitle && <RatingLayout reviewTitle={reviewTitle} ratingValue={reviewData?.value} ratingLabel={reviewData?.label}/>}
131
137
  </View>
132
138
  );
133
139
  };
@@ -29,7 +29,6 @@ export const storyHeaderStyles = (COLORS, FONT_FAMILY) => StyleSheet.create({
29
29
  color: COLORS.BRAND_BLACK,
30
30
  fontFamily: FONT_FAMILY.secondary,
31
31
  opacity: 0.8,
32
- marginBottom: 5,
33
32
  },
34
33
  attributionStyle: {
35
34
  color: COLORS.BRAND_BLACK,
@@ -17,9 +17,9 @@ export const storyTitleStyles = ({ COLORS, FONT_SIZE }) => StyleSheet.create({
17
17
  marginTop: 15,
18
18
  paddingVertical: 5,
19
19
  },
20
- premiumIcon : {
21
- paddingLeft:10,
22
- marginRight:-5,
20
+ premiumIcon : {
21
+ paddingLeft:10,
22
+ marginRight:-5,
23
23
  borderWidth:1
24
24
  }
25
25
  });
@@ -10,12 +10,50 @@ export const YouTubePlayer = ({ card = {} }) => {
10
10
  const windowWidth = Dimensions.get("window").width;
11
11
  const styles = webviewStyles(windowWidth);
12
12
 
13
+ const getYoutubeIframe = (ytEmbedURL) => {
14
+ const videoId = ytEmbedURL.split('/').pop();
15
+ const width = windowWidth - 20;
16
+ const height = (9/16)*(windowWidth - 20);
17
+ const htmlContent = `
18
+ <iframe
19
+ width="1280"
20
+ height="720"
21
+ src="https://www.youtube.com/embed/${videoId}"
22
+ frameborder="0"
23
+ allow="accelerometer;
24
+ autoplay;
25
+ clipboard-write;
26
+ encrypted-media;
27
+ gyroscope;
28
+ picture-in-picture;
29
+ web-share"
30
+ referrerpolicy="strict-origin-when-cross-origin"
31
+ allowfullscreen
32
+ ></iframe>`;
33
+
34
+ return `
35
+ <html>
36
+ <head>
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ <style>
39
+ iframe{
40
+ width:${width}px;
41
+ height:${height}px;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ ${htmlContent}
47
+ </body>
48
+ </html>`;
49
+ }
50
+
13
51
  return (
14
52
  <View style={styles.container} testID="youtube-player-id">
15
53
  <WebView
16
54
  mediaPlaybackRequiresUserAction
17
55
  style={styles.webViewContainer}
18
- source={{ uri: ytEmbedURL }}
56
+ source={{ html: getYoutubeIframe(ytEmbedURL), baseUrl:'https://www.youtube.com'}}
19
57
  javaScriptEnabled
20
58
  domStorageEnabled
21
59
  startInLoadingState
@@ -2,7 +2,6 @@ export const hexToRgb = (hex, alpha=1) => {
2
2
  let rgbCode = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,(m, r, g, b) => '#' + r + r + g + g + b + b)
3
3
  .substring(1).match(/.{2}/g)
4
4
  .map(x => parseInt(x, 16));
5
- console.log('risi - color', `rgb(${rgbCode[0]}, ${rgbCode[1]}, ${rgbCode[2]}, ${alpha})`)
6
5
  return `rgba(${rgbCode[0]}, ${rgbCode[1]}, ${rgbCode[2]}, ${alpha})`;
7
6
  }
8
7
 
@@ -38,25 +38,54 @@ export const getFirstVideoElement = (cards) => cards
38
38
  return acc;
39
39
  }, [])
40
40
  .find(
41
- ({ type, subtype }) => type === 'youtube-video'
42
- || (type === 'jsembed' && subtype === 'dailymotion-video'),
41
+ (ele) => ele?.type === 'youtube-video'
42
+ || (ele?.type === 'jsembed' && ele?.subtype === 'dailymotion-video'),
43
43
  ) || {};
44
44
 
45
- export const mutateDataBeforeAccess = (item, storyHasAccess) => {
45
+ export const mutateDataBeforeAccess = (item, storyHasAccess, visibleCardCountForBlockedStories) => {
46
46
  if (isStoryFree(item) === 0 || storyHasAccess === 'granted') {
47
47
  return item;
48
48
  }
49
49
  if (storyHasAccess !== 'loading' && storyHasAccess === 'granted') {
50
50
  return item;
51
51
  }
52
- /* The following steps are necessary to keep argument mutation at bay */
53
- const firstCardObject = get(item, ['cards', 0], {});
52
+ const firstCardObject = get(item, ['cards', 2], {});
54
53
  const firstCardElement = get(firstCardObject, !STORY_TYPES.VIDEO_STORY && ['story-elements', 0], {});
54
+ let visibleContentsForBlockedStory = [];
55
+ let showBlockingWall = false;
56
+ let visibleStoryCount = 0;
57
+ if(!visibleCardCountForBlockedStories){
58
+ return {
59
+ cards: visibleContentsForBlockedStory,
60
+ showBlockingWall: true
61
+ }
62
+ }
63
+
64
+ if(item.cards && item.cards.length > 0){
65
+ for(let i = 0; i < item.cards.length; i++){
66
+ let storyElement = [];
67
+ for(let j=0; j<item.cards[i]['story-elements'].length; j++){
68
+ let tempStoryElement = item.cards[i]['story-elements'][j];
69
+ if(visibleStoryCount < visibleCardCountForBlockedStories-1){
70
+ storyElement.push(tempStoryElement);
71
+ visibleStoryCount++;
72
+ } else {
73
+ showBlockingWall = true;
74
+ storyElement.push(tempStoryElement);
75
+ break;
76
+ }
77
+ }
78
+
79
+ visibleContentsForBlockedStory.push({...item.cards[i],'story-elements' : storyElement});
80
+ if(showBlockingWall) break;
81
+ }
82
+ }
55
83
 
56
84
  return {
57
85
  ...item,
58
86
  ...{
59
- cards: [{ ...firstCardObject, 'story-elements': [firstCardElement] }],
87
+ cards: visibleContentsForBlockedStory,
60
88
  },
89
+ showBlockingWall
61
90
  };
62
91
  };