@quintype/native-components 2.20.5 → 2.20.7
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 +9 -0
- package/package.json +1 -1
- package/src/components/JSEmbedElement/index.js +39 -6
- package/src/components/LightBoxImage/index.js +16 -4
- package/src/components/ResponsiveImage/index.js +16 -10
- package/src/components/SlideshowStoryCard/index.js +24 -13
- package/src/components/SlideshowStoryCard/styles.js +23 -25
- package/src/components/StoryGallery/index.js +230 -24
- package/src/components/StoryGallery/styles.js +131 -32
- package/src/components/StoryHeader/index.js +0 -1
- package/src/components/StorySlideshow/index.js +29 -25
- package/src/components/StorySlideshow/styles.js +35 -39
- package/src/utils/imageUtils.js +20 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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.7](https://github.com/quintype/native-components/compare/v2.20.6...v2.20.7) (2023-06-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **images:** Fixes crop issues and adds rich-text support for attribution ([#201](https://github.com/quintype/native-components/issues/201)) ([c0a5ca0](https://github.com/quintype/native-components/commit/c0a5ca0f593f1ce3459ae0fcc2aba3f06f21e053))
|
|
11
|
+
|
|
12
|
+
### [2.20.6](https://github.com/quintype/native-components/compare/v2.20.5...v2.20.6) (2023-06-15)
|
|
13
|
+
|
|
5
14
|
### [2.20.5](https://github.com/quintype/native-components/compare/v2.20.4...v2.20.5) (2023-03-21)
|
|
6
15
|
|
|
7
16
|
### [2.20.4](https://github.com/quintype/native-components/compare/v2.20.3...v2.20.4) (2023-02-01)
|
package/package.json
CHANGED
|
@@ -17,10 +17,30 @@ const replaceDefaultProtocol = (content) => {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
const removeWidthnHeight = (htmlContent) => {
|
|
21
|
+
const temp = htmlContent.split(' ');
|
|
22
|
+
let finalHtml = '';
|
|
23
|
+
if (temp[0] === '<iframe') {
|
|
24
|
+
for (let i = 0; i < temp.length; i++) {
|
|
25
|
+
if (
|
|
26
|
+
(temp[i].includes('width') || temp[i].includes('height'))
|
|
27
|
+
&& !temp[i].includes('/')
|
|
28
|
+
) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
finalHtml = `${finalHtml + temp[i]} `;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return finalHtml;
|
|
35
|
+
}
|
|
36
|
+
return htmlContent;
|
|
37
|
+
};
|
|
38
|
+
|
|
20
39
|
const getHTMLContent = (embedJs) => {
|
|
40
|
+
const { width } = Dimensions.get('window');
|
|
21
41
|
const decodedContent = getDecodedContent(embedJs);
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
let htmlContent = replaceDefaultProtocol(decodedContent);
|
|
43
|
+
htmlContent = removeWidthnHeight(htmlContent);
|
|
24
44
|
const webViewScript = `
|
|
25
45
|
<script type="application/javascript">
|
|
26
46
|
var interValId;
|
|
@@ -41,6 +61,12 @@ const getHTMLContent = (embedJs) => {
|
|
|
41
61
|
<html>
|
|
42
62
|
<head>
|
|
43
63
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
64
|
+
<style>
|
|
65
|
+
iframe{
|
|
66
|
+
width:${width - 20}px;
|
|
67
|
+
height:${width - 20}px;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
44
70
|
</head>
|
|
45
71
|
<body>
|
|
46
72
|
${htmlContent}
|
|
@@ -52,7 +78,11 @@ const getHTMLContent = (embedJs) => {
|
|
|
52
78
|
export const JSEmbedElement = (props) => {
|
|
53
79
|
const [height, setHeight] = useState(300);
|
|
54
80
|
const webViewRef = useRef(null);
|
|
55
|
-
const width = get(
|
|
81
|
+
const width = get(
|
|
82
|
+
props,
|
|
83
|
+
['currentLayout', 'width'],
|
|
84
|
+
Dimensions.get('window').width,
|
|
85
|
+
);
|
|
56
86
|
|
|
57
87
|
useEffect(() => {
|
|
58
88
|
webViewRef?.current?.reload();
|
|
@@ -71,10 +101,10 @@ export const JSEmbedElement = (props) => {
|
|
|
71
101
|
|
|
72
102
|
const baseUrl = urlMapper[props.element.subtype] || 'https://twitter.com';
|
|
73
103
|
|
|
74
|
-
return
|
|
104
|
+
return {
|
|
75
105
|
html: getHTMLContent(props.element['embed-js']),
|
|
76
106
|
baseUrl,
|
|
77
|
-
}
|
|
107
|
+
};
|
|
78
108
|
};
|
|
79
109
|
|
|
80
110
|
return (
|
|
@@ -82,7 +112,10 @@ export const JSEmbedElement = (props) => {
|
|
|
82
112
|
<WebView
|
|
83
113
|
ref={webViewRef}
|
|
84
114
|
style={{
|
|
85
|
-
width,
|
|
115
|
+
width,
|
|
116
|
+
height,
|
|
117
|
+
flex: 0,
|
|
118
|
+
opacity: 0.99,
|
|
86
119
|
}}
|
|
87
120
|
automaticallyAdjustContentInsets={false}
|
|
88
121
|
scrollEnabled={false}
|
|
@@ -6,15 +6,23 @@ import { LightBox } from '../LightBox';
|
|
|
6
6
|
import { getImageHeight } from '../../utils';
|
|
7
7
|
|
|
8
8
|
export const LightBoxImage = ({
|
|
9
|
-
data, hero, currentLayout, additionalStyles,
|
|
9
|
+
data, hero, currentLayout, additionalStyles, elementType, onClickHandler = () => {}, index,
|
|
10
10
|
}) => {
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
cdn, slug, metaData, imageWidth: previewWidth,
|
|
13
|
+
} = data;
|
|
12
14
|
const { width, height } = metaData || {};
|
|
13
15
|
const imageHeight = getImageHeight(width, height);
|
|
14
16
|
const imageUri = `${data?.cdn}/${data?.slug}`;
|
|
15
17
|
const [showModal, setShowModal] = useState(false);
|
|
16
18
|
|
|
17
|
-
const toggleModal = () =>
|
|
19
|
+
const toggleModal = () => {
|
|
20
|
+
if (elementType === 'gallery') {
|
|
21
|
+
onClickHandler(index);
|
|
22
|
+
} else {
|
|
23
|
+
setShowModal(!showModal);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
18
26
|
|
|
19
27
|
const styles = {
|
|
20
28
|
image: {
|
|
@@ -34,6 +42,8 @@ export const LightBoxImage = ({
|
|
|
34
42
|
metaData={metaData}
|
|
35
43
|
hero={hero || false}
|
|
36
44
|
styles={cumulativeStyles}
|
|
45
|
+
imageWidth={previewWidth}
|
|
46
|
+
elementType={elementType || null}
|
|
37
47
|
/>
|
|
38
48
|
</TouchableOpacity>
|
|
39
49
|
</>
|
|
@@ -41,9 +51,11 @@ export const LightBoxImage = ({
|
|
|
41
51
|
};
|
|
42
52
|
|
|
43
53
|
LightBoxImage.propTypes = {
|
|
44
|
-
children: PropTypes.element,
|
|
45
54
|
data: PropTypes.object,
|
|
46
55
|
hero: PropTypes.bool,
|
|
47
56
|
currentLayout: PropTypes.object,
|
|
48
57
|
additionalStyles: PropTypes.object,
|
|
58
|
+
elementType: PropTypes.string,
|
|
59
|
+
onClickHandler: PropTypes.func,
|
|
60
|
+
index: PropTypes.number,
|
|
49
61
|
};
|
|
@@ -5,18 +5,18 @@ import React, {
|
|
|
5
5
|
import { StyleSheet, View } from 'react-native';
|
|
6
6
|
import FastImage from 'react-native-fast-image';
|
|
7
7
|
import Icon from 'react-native-vector-icons/FontAwesome';
|
|
8
|
-
import { isTablet } from 'react-native-device-info';
|
|
9
8
|
import { FallbackIcon } from '../../Icons/FallBackIcon';
|
|
10
9
|
import { AppTheme } from '../../utils';
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getImageURL,
|
|
12
|
+
getCustomResolutionImageURL,
|
|
13
|
+
} from '../../utils/imageUtils';
|
|
12
14
|
|
|
13
15
|
const ResponsiveImageBase = (props) => {
|
|
14
16
|
const [placeholder, setPlaceholder] = useState(true);
|
|
15
17
|
const { theme } = useContext(AppTheme);
|
|
16
18
|
const { COLORS, CustomFallBackIcon, CustomFallBackBackground } = theme;
|
|
17
19
|
|
|
18
|
-
const HERO_IMAGE_HEIGHT = isTablet() ? 300 : 232;
|
|
19
|
-
|
|
20
20
|
const flattenedImageStyle = StyleSheet.flatten([
|
|
21
21
|
styles.defaultImage,
|
|
22
22
|
props.styles,
|
|
@@ -24,10 +24,8 @@ const ResponsiveImageBase = (props) => {
|
|
|
24
24
|
width: props.imageWidth,
|
|
25
25
|
height: (props.imageWidth * 9) / 16,
|
|
26
26
|
},
|
|
27
|
-
props
|
|
28
|
-
|
|
29
|
-
height: HERO_IMAGE_HEIGHT,
|
|
30
|
-
alignSelf: 'stretch',
|
|
27
|
+
props?.elementType === 'gallery' && {
|
|
28
|
+
backgroundColor: COLORS.BRAND_BLACK,
|
|
31
29
|
},
|
|
32
30
|
]);
|
|
33
31
|
|
|
@@ -43,7 +41,14 @@ const ResponsiveImageBase = (props) => {
|
|
|
43
41
|
) : (
|
|
44
42
|
<FallbackIcon />
|
|
45
43
|
);
|
|
46
|
-
|
|
44
|
+
|
|
45
|
+
let imageUrl = '';
|
|
46
|
+
|
|
47
|
+
if (props?.elementType === 'gallery') {
|
|
48
|
+
imageUrl = getCustomResolutionImageURL(props, [1, 1]);
|
|
49
|
+
} else {
|
|
50
|
+
imageUrl = getImageURL(props);
|
|
51
|
+
}
|
|
47
52
|
|
|
48
53
|
const userFallback = () => <Icon name="user" size={20} />;
|
|
49
54
|
|
|
@@ -71,7 +76,7 @@ const ResponsiveImageBase = (props) => {
|
|
|
71
76
|
style={flattenedImageStyle}
|
|
72
77
|
source={sourceURI}
|
|
73
78
|
onLoad={onLoadHandler}
|
|
74
|
-
resizeMode={FastImage.resizeMode.cover}
|
|
79
|
+
resizeMode={props?.elementType === 'gallery' && !(props.hero) ? FastImage.resizeMode.contain : FastImage.resizeMode.cover}
|
|
75
80
|
{...props}
|
|
76
81
|
/>
|
|
77
82
|
{placeholder && (
|
|
@@ -94,6 +99,7 @@ ResponsiveImageBase.propTypes = {
|
|
|
94
99
|
testID: PropTypes.string,
|
|
95
100
|
containerTestID: PropTypes.string,
|
|
96
101
|
imageWidth: PropTypes.number,
|
|
102
|
+
elementType: PropTypes.string,
|
|
97
103
|
};
|
|
98
104
|
|
|
99
105
|
ResponsiveImageBase.defaultProps = {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React, { useContext } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { View, Linking } from 'react-native';
|
|
4
|
+
import HTML from 'react-native-render-html';
|
|
4
5
|
import { AppTheme, getScreenPercentageWidth } from '../../utils/index';
|
|
5
6
|
import { LightBoxImage } from '../LightBoxImage';
|
|
6
7
|
import { slideshowStoryCardStyles } from './styles';
|
|
@@ -9,26 +10,37 @@ export const SlideshowStoryCard = ({ card, cdn }) => {
|
|
|
9
10
|
const attribution = card['image-attribution'];
|
|
10
11
|
const { title } = card;
|
|
11
12
|
const { theme } = useContext(AppTheme);
|
|
12
|
-
const
|
|
13
|
-
const styles = slideshowStoryCardStyles(theme)
|
|
13
|
+
const { CAN_COPY_TEXT } = theme;
|
|
14
|
+
const styles = slideshowStoryCardStyles(theme);
|
|
14
15
|
const imgData = {
|
|
15
16
|
cdn,
|
|
16
17
|
slug: card['image-s3-key'],
|
|
17
|
-
imageWidth: getScreenPercentageWidth(
|
|
18
|
+
imageWidth: getScreenPercentageWidth(100),
|
|
18
19
|
metaData: card['image-metadata'],
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<View style={styles.container}>
|
|
23
|
-
<
|
|
24
|
-
<LightBoxImage data={imgData} hero />
|
|
25
|
-
</View>
|
|
26
|
-
|
|
24
|
+
<LightBoxImage data={imgData} />
|
|
27
25
|
<View style={styles.textContainer}>
|
|
28
|
-
<
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
<HTML
|
|
27
|
+
html={title}
|
|
28
|
+
textSelectable={CAN_COPY_TEXT}
|
|
29
|
+
key={Math.random()}
|
|
30
|
+
baseFontStyle={styles.cardTitleText}
|
|
31
|
+
onLinkPress={(e, href) => {
|
|
32
|
+
Linking.openURL(href);
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<HTML
|
|
36
|
+
html={attribution}
|
|
37
|
+
textSelectable={CAN_COPY_TEXT}
|
|
38
|
+
key={Math.random()}
|
|
39
|
+
baseFontStyle={styles.attributionText}
|
|
40
|
+
onLinkPress={(e, href) => {
|
|
41
|
+
Linking.openURL(href);
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
32
44
|
</View>
|
|
33
45
|
</View>
|
|
34
46
|
);
|
|
@@ -37,5 +49,4 @@ export const SlideshowStoryCard = ({ card, cdn }) => {
|
|
|
37
49
|
SlideshowStoryCard.propTypes = {
|
|
38
50
|
cdn: PropTypes.string.isRequired,
|
|
39
51
|
card: PropTypes.object,
|
|
40
|
-
imageSize: PropTypes.object,
|
|
41
52
|
};
|
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
import { StyleSheet, Dimensions } from 'react-native';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
const { width } = Dimensions.get('window');
|
|
4
4
|
|
|
5
|
-
export const slideshowStoryCardStyles = ({ COLORS }) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
28
|
-
}
|
|
5
|
+
export const slideshowStoryCardStyles = ({ COLORS, FONT_FAMILY }) => StyleSheet.create({
|
|
6
|
+
textContainer: {
|
|
7
|
+
paddingHorizontal: 10,
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
flexWrap: 'wrap',
|
|
10
|
+
marginTop: 10,
|
|
11
|
+
},
|
|
12
|
+
cardTitleText: {
|
|
13
|
+
opacity: 0.7,
|
|
14
|
+
color: COLORS.BRAND_BLACK,
|
|
15
|
+
fontFamily: FONT_FAMILY.secondary,
|
|
16
|
+
},
|
|
17
|
+
container: {
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
width,
|
|
20
|
+
},
|
|
21
|
+
attributionText: {
|
|
22
|
+
color: COLORS.BRAND_BLACK,
|
|
23
|
+
fontFamily: FONT_FAMILY.secondary,
|
|
24
|
+
opacity: 0.8,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -1,39 +1,245 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
|
-
import React, { useContext } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import React, { useContext, useState, useRef } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
View,
|
|
5
|
+
Modal,
|
|
6
|
+
Linking,
|
|
7
|
+
SafeAreaView,
|
|
8
|
+
FlatList,
|
|
9
|
+
TouchableOpacity,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import Icon from 'react-native-vector-icons/FontAwesome';
|
|
12
|
+
import FastImage from 'react-native-fast-image';
|
|
13
|
+
import HTML from 'react-native-render-html';
|
|
4
14
|
import { styles } from './styles';
|
|
5
15
|
import { Text } from '../index';
|
|
6
|
-
import {
|
|
16
|
+
import { AppTheme } from '../../utils/index';
|
|
7
17
|
import { LightBoxImage } from '../LightBoxImage';
|
|
8
18
|
|
|
9
19
|
export const StoryGallery = ({ cdn, card }) => {
|
|
10
20
|
const { theme } = useContext(AppTheme);
|
|
21
|
+
const { COLORS, DARK_MODE, CAN_COPY_TEXT } = theme;
|
|
11
22
|
const galleryStyles = styles(theme);
|
|
12
23
|
const storyElements = card['story-elements'];
|
|
24
|
+
const [showModal, setShowModal] = useState(false);
|
|
25
|
+
|
|
26
|
+
const [currentIndex, setCurrentTabIndex] = useState(0);
|
|
27
|
+
|
|
28
|
+
const flatlistRef = useRef();
|
|
29
|
+
const [index, setIndex] = useState(0);
|
|
30
|
+
const totalSlides = card['story-elements']?.length;
|
|
31
|
+
const lastSlide = currentIndex + 1 === totalSlides;
|
|
32
|
+
const firstSlide = currentIndex === 0;
|
|
33
|
+
const [galleryLastImgUrl, setGalleryLastImageUrl] = useState('');
|
|
34
|
+
|
|
35
|
+
const onPressHandler = (index = 5) => {
|
|
36
|
+
setIndex(index);
|
|
37
|
+
setShowModal(true);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const moveNext = () => {
|
|
41
|
+
if (!lastSlide) {
|
|
42
|
+
flatlistRef.current.scrollToIndex({
|
|
43
|
+
index: Math.floor(Math.min(currentIndex + 1, totalSlides - 1)),
|
|
44
|
+
});
|
|
45
|
+
setCurrentTabIndex(currentIndex + 1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const movePrev = () => {
|
|
50
|
+
if (!firstSlide) {
|
|
51
|
+
flatlistRef.current.scrollToIndex({
|
|
52
|
+
index: Math.ceil(Math.max(currentIndex - 1, 0)),
|
|
53
|
+
});
|
|
54
|
+
setCurrentTabIndex(Math.max(currentIndex - 1, 0));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const showArrow = (direction) => {
|
|
59
|
+
const arrowStyles = direction === 'left' ? galleryStyles.leftArrow : galleryStyles.rightArrow;
|
|
60
|
+
const onPressHandler = direction === 'left' ? movePrev : moveNext;
|
|
61
|
+
const iconName = direction === 'left' ? 'chevron-left' : 'chevron-right';
|
|
62
|
+
return (
|
|
63
|
+
<View style={arrowStyles}>
|
|
64
|
+
<TouchableOpacity onPress={onPressHandler}>
|
|
65
|
+
<Icon name={iconName} size={22} color={COLORS?.MONO7} />
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleScroll = (event) => {
|
|
72
|
+
const xOffset = event.nativeEvent?.contentOffset?.x;
|
|
73
|
+
const contentWidth = event.nativeEvent?.contentSize?.width;
|
|
74
|
+
const layoutWidth = event.nativeEvent?.layoutMeasurement?.width;
|
|
75
|
+
const value = xOffset / contentWidth;
|
|
76
|
+
const leftThreshold = contentWidth / (2 * totalSlides);
|
|
77
|
+
const rightThreshold = contentWidth - 1.5 * layoutWidth;
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
leftThreshold <= xOffset
|
|
81
|
+
&& (layoutWidth > xOffset || rightThreshold > xOffset)
|
|
82
|
+
) {
|
|
83
|
+
setCurrentTabIndex(value * totalSlides);
|
|
84
|
+
} else if (xOffset < leftThreshold) {
|
|
85
|
+
setCurrentTabIndex(0);
|
|
86
|
+
} else if (xOffset >= rightThreshold) {
|
|
87
|
+
setCurrentTabIndex(totalSlides - 1);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const closeModalHandler = () => {
|
|
92
|
+
setShowModal(false);
|
|
93
|
+
setCurrentTabIndex(0);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const renderItem = (item) => {
|
|
97
|
+
const data = {
|
|
98
|
+
cdn,
|
|
99
|
+
slug: item.item['image-s3-key'],
|
|
100
|
+
metaData: item.item['image-metadata'],
|
|
101
|
+
};
|
|
102
|
+
const { title } = item.item;
|
|
103
|
+
const attribution = item.item['image-attribution'];
|
|
104
|
+
const imageUri = `${data?.cdn}/${data?.slug}`;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<View style={galleryStyles.carouselContainer}>
|
|
108
|
+
<FastImage
|
|
109
|
+
source={{ uri: imageUri }}
|
|
110
|
+
resizeMode={FastImage.resizeMode.contain}
|
|
111
|
+
style={galleryStyles.carouselImage}
|
|
112
|
+
/>
|
|
113
|
+
<HTML
|
|
114
|
+
html={title}
|
|
115
|
+
textSelectable={CAN_COPY_TEXT}
|
|
116
|
+
key={Math.random()}
|
|
117
|
+
baseFontStyle={galleryStyles.titleText}
|
|
118
|
+
onLinkPress={(e, href) => {
|
|
119
|
+
Linking.openURL(href);
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
<HTML
|
|
123
|
+
html={attribution}
|
|
124
|
+
textSelectable={CAN_COPY_TEXT}
|
|
125
|
+
key={Math.random()}
|
|
126
|
+
baseFontStyle={galleryStyles.attributionText}
|
|
127
|
+
onLinkPress={(e, href) => {
|
|
128
|
+
Linking.openURL(href);
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
13
134
|
|
|
14
135
|
return (
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
136
|
+
<>
|
|
137
|
+
<View style={galleryStyles.container}>
|
|
138
|
+
<Text style={galleryStyles.titleText}>{card.title}</Text>
|
|
139
|
+
|
|
140
|
+
<View style={galleryStyles.imgContainer}>
|
|
141
|
+
{storyElements.map((element, index) => {
|
|
142
|
+
const data = {
|
|
143
|
+
cdn,
|
|
144
|
+
slug: element['image-s3-key'],
|
|
145
|
+
metaData: element['image-metadata'],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (storyElements.length > 6 && index >= 5) {
|
|
149
|
+
const imageUri = `${data?.cdn}/${data?.slug}`;
|
|
150
|
+
if (galleryLastImgUrl === '') {
|
|
151
|
+
setGalleryLastImageUrl(imageUri);
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const style = galleryStyles.fullWidth;
|
|
157
|
+
return (
|
|
158
|
+
<TouchableOpacity>
|
|
159
|
+
<LightBoxImage
|
|
160
|
+
onClickHandler={onPressHandler}
|
|
161
|
+
hero={!!data.metaData['focus-point']}
|
|
162
|
+
key={Math.random()}
|
|
163
|
+
data={data}
|
|
164
|
+
additionalStyles={style}
|
|
165
|
+
elementType={card?.metadata?.type}
|
|
166
|
+
index={index}
|
|
167
|
+
/>
|
|
168
|
+
</TouchableOpacity>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
{storyElements && storyElements.length > 6 && (
|
|
172
|
+
<TouchableOpacity
|
|
173
|
+
onPress={() => onPressHandler(5)}
|
|
174
|
+
style={[
|
|
175
|
+
galleryStyles.fullWidth,
|
|
176
|
+
galleryStyles.showMoreImageContainer,
|
|
177
|
+
]}
|
|
178
|
+
>
|
|
179
|
+
<FastImage
|
|
180
|
+
source={{ uri: galleryLastImgUrl }}
|
|
181
|
+
resizeMode={FastImage.resizeMode.cover}
|
|
182
|
+
style={galleryStyles.showMoreImage}
|
|
183
|
+
/>
|
|
184
|
+
<Text style={galleryStyles.showMoreText}>
|
|
185
|
+
+
|
|
186
|
+
{storyElements.length - 5}
|
|
187
|
+
</Text>
|
|
188
|
+
</TouchableOpacity>
|
|
189
|
+
)}
|
|
190
|
+
</View>
|
|
191
|
+
<Text style={galleryStyles.descText} lato>
|
|
192
|
+
{card.description}
|
|
193
|
+
</Text>
|
|
32
194
|
</View>
|
|
33
|
-
<
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
195
|
+
<Modal
|
|
196
|
+
visible={showModal}
|
|
197
|
+
animationType="slide"
|
|
198
|
+
transparent={false}
|
|
199
|
+
supportedOrientations={['portrait', 'landscape']}
|
|
200
|
+
onRequestClose={closeModalHandler}
|
|
201
|
+
onBackdropPress={closeModalHandler}
|
|
202
|
+
>
|
|
203
|
+
<SafeAreaView style={galleryStyles.safeAreaView}>
|
|
204
|
+
<View style={galleryStyles.modalContainer}>
|
|
205
|
+
<TouchableOpacity onPress={closeModalHandler}>
|
|
206
|
+
<View style={galleryStyles.close}>
|
|
207
|
+
<Icon
|
|
208
|
+
name="times"
|
|
209
|
+
size={20}
|
|
210
|
+
color={DARK_MODE ? COLORS.BRAND_BLACK : COLORS.BRAND_WHITE}
|
|
211
|
+
/>
|
|
212
|
+
</View>
|
|
213
|
+
</TouchableOpacity>
|
|
214
|
+
<View style={galleryStyles.wrapper}>
|
|
215
|
+
<FlatList
|
|
216
|
+
ref={flatlistRef}
|
|
217
|
+
data={card['story-elements']}
|
|
218
|
+
renderItem={renderItem}
|
|
219
|
+
keyExtractor={(item) => item.id}
|
|
220
|
+
horizontal
|
|
221
|
+
extraData={currentIndex}
|
|
222
|
+
onScroll={handleScroll}
|
|
223
|
+
initialScrollIndex={index}
|
|
224
|
+
onScrollToIndexFailed={(info) => {
|
|
225
|
+
const wait = new Promise((resolve) => setTimeout(resolve, 500));
|
|
226
|
+
wait.then(() => {
|
|
227
|
+
flatlistRef.current?.scrollToIndex({
|
|
228
|
+
index: info.index,
|
|
229
|
+
animated: true,
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}}
|
|
233
|
+
/>
|
|
234
|
+
|
|
235
|
+
{!firstSlide && showArrow('left')}
|
|
236
|
+
|
|
237
|
+
{!lastSlide && showArrow('right')}
|
|
238
|
+
</View>
|
|
239
|
+
</View>
|
|
240
|
+
</SafeAreaView>
|
|
241
|
+
</Modal>
|
|
242
|
+
</>
|
|
37
243
|
);
|
|
38
244
|
};
|
|
39
245
|
|
|
@@ -1,35 +1,134 @@
|
|
|
1
1
|
import { StyleSheet, Dimensions } from 'react-native';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { AppTheme } from '../../utils';
|
|
2
4
|
|
|
3
|
-
const {
|
|
5
|
+
export const styles = ({ FONT_SIZE }) => {
|
|
6
|
+
const { width: deviceWidth } = Dimensions.get('window');
|
|
7
|
+
const { theme } = useContext(AppTheme);
|
|
8
|
+
const { COLORS, DARK_MODE, FONT_FAMILY } = theme;
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
10
|
+
return StyleSheet.create({
|
|
11
|
+
container: {
|
|
12
|
+
width: '100%',
|
|
13
|
+
paddingHorizontal: 10,
|
|
14
|
+
},
|
|
15
|
+
imgContainer: {
|
|
16
|
+
width: '100%',
|
|
17
|
+
flexDirection: 'row',
|
|
18
|
+
flexWrap: 'wrap',
|
|
19
|
+
justifyContent: 'space-between',
|
|
20
|
+
},
|
|
21
|
+
halfWidth: {
|
|
22
|
+
width: (deviceWidth - 30) / 2,
|
|
23
|
+
height: 124,
|
|
24
|
+
},
|
|
25
|
+
fullWidth: {
|
|
26
|
+
width: (deviceWidth - 30) / 3,
|
|
27
|
+
height: (deviceWidth - 30) / 3,
|
|
28
|
+
marginVertical: 2.5,
|
|
29
|
+
},
|
|
30
|
+
descText: {
|
|
31
|
+
fontSize: FONT_SIZE.h2,
|
|
32
|
+
opacity: 0.8,
|
|
33
|
+
paddingTop: 10,
|
|
34
|
+
},
|
|
35
|
+
modalContainer: {
|
|
36
|
+
backgroundColor: DARK_MODE ? COLORS.MONO6 : COLORS.BRAND_BLACK,
|
|
37
|
+
flex: 1,
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
},
|
|
40
|
+
safeAreaView: {
|
|
41
|
+
flex: 1,
|
|
42
|
+
backgroundColor: 'transparent',
|
|
43
|
+
},
|
|
44
|
+
close: {
|
|
45
|
+
justifyContent: 'flex-start',
|
|
46
|
+
padding: 10,
|
|
47
|
+
height: 40,
|
|
48
|
+
zIndex: 1,
|
|
49
|
+
marginTop: 25,
|
|
50
|
+
},
|
|
51
|
+
imageContainer: {
|
|
52
|
+
justifyContent: 'center',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
width: '100%',
|
|
55
|
+
height: '100%',
|
|
56
|
+
},
|
|
57
|
+
portrait: {
|
|
58
|
+
width: '100%',
|
|
59
|
+
height: '80%',
|
|
60
|
+
},
|
|
61
|
+
wrapper: {
|
|
62
|
+
paddingHorizontal: 10,
|
|
63
|
+
flex: 1,
|
|
64
|
+
backgroundColor: 'black',
|
|
65
|
+
width: '100%',
|
|
66
|
+
},
|
|
67
|
+
titleText: {
|
|
68
|
+
fontWeight: 'normal',
|
|
69
|
+
fontSize: FONT_SIZE.h3,
|
|
70
|
+
opacity: 0.8,
|
|
71
|
+
color: 'white',
|
|
72
|
+
marginTop: 10,
|
|
73
|
+
marginHorizontal: 10,
|
|
74
|
+
fontFamily: FONT_FAMILY.secondary,
|
|
75
|
+
},
|
|
76
|
+
attributionText: {
|
|
77
|
+
fontWeight: 'normal',
|
|
78
|
+
marginBottom: 10,
|
|
79
|
+
fontSize: FONT_SIZE.h3,
|
|
80
|
+
opacity: 0.8,
|
|
81
|
+
color: 'white',
|
|
82
|
+
marginHorizontal: 10,
|
|
83
|
+
fontFamily: FONT_FAMILY.secondary,
|
|
84
|
+
},
|
|
85
|
+
leftArrow: {
|
|
86
|
+
position: 'absolute',
|
|
87
|
+
top: '46%',
|
|
88
|
+
right: '95%',
|
|
89
|
+
left: 20,
|
|
90
|
+
zIndex: 999,
|
|
91
|
+
width: 30,
|
|
92
|
+
height: 40,
|
|
93
|
+
backgroundColor: COLORS.TRANSPARENT_BLACK,
|
|
94
|
+
display: 'flex',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
justifyContent: 'center',
|
|
97
|
+
},
|
|
98
|
+
rightArrow: {
|
|
99
|
+
position: 'absolute',
|
|
100
|
+
top: '46%',
|
|
101
|
+
left: '93%',
|
|
102
|
+
right: 20,
|
|
103
|
+
zIndex: 999,
|
|
104
|
+
width: 30,
|
|
105
|
+
height: 40,
|
|
106
|
+
backgroundColor: COLORS.TRANSPARENT_BLACK,
|
|
107
|
+
display: 'flex',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
justifyContent: 'center',
|
|
110
|
+
},
|
|
111
|
+
carouselImage: {
|
|
112
|
+
width: '100%',
|
|
113
|
+
height: '80%',
|
|
114
|
+
},
|
|
115
|
+
carouselContainer: {
|
|
116
|
+
width: deviceWidth - 10,
|
|
117
|
+
margin: 10,
|
|
118
|
+
},
|
|
119
|
+
showMoreImageContainer: {
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
},
|
|
123
|
+
showMoreImage: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
height: '100%',
|
|
126
|
+
opacity: 0.5,
|
|
127
|
+
},
|
|
128
|
+
showMoreText: {
|
|
129
|
+
fontSize: 30,
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
color: COLORS.BRAND_WHITE,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import PropTypes from
|
|
2
|
-
import React, { useContext, useRef, useState } from
|
|
3
|
-
import { TouchableOpacity, View } from
|
|
4
|
-
import { FlatList } from
|
|
5
|
-
import Icon from
|
|
6
|
-
import { AppTheme } from
|
|
7
|
-
import { Text } from
|
|
8
|
-
import { SlideshowStoryCard } from
|
|
9
|
-
import { storySlideshowStyles } from
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import React, { useContext, useRef, useState } from 'react';
|
|
3
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import { FlatList } from 'react-native-gesture-handler';
|
|
5
|
+
import Icon from 'react-native-vector-icons/AntDesign';
|
|
6
|
+
import { AppTheme } from '../../utils';
|
|
7
|
+
import { Text } from '../index';
|
|
8
|
+
import { SlideshowStoryCard } from '../SlideshowStoryCard';
|
|
9
|
+
import { storySlideshowStyles } from './styles';
|
|
10
10
|
|
|
11
11
|
export const StorySlideshow = ({ card, cdn }) => {
|
|
12
12
|
const { theme } = useContext(AppTheme);
|
|
@@ -16,13 +16,13 @@ export const StorySlideshow = ({ card, cdn }) => {
|
|
|
16
16
|
|
|
17
17
|
const flatlistRef = useRef();
|
|
18
18
|
|
|
19
|
-
const totalSlides = card[
|
|
19
|
+
const totalSlides = card['story-elements']?.length;
|
|
20
20
|
const lastSlide = currentIndex + 1 === totalSlides;
|
|
21
21
|
const firstSlide = currentIndex === 0;
|
|
22
22
|
|
|
23
23
|
const moveNext = () => {
|
|
24
24
|
if (!lastSlide) {
|
|
25
|
-
flatlistRef.current.scrollToIndex({ index: Math.floor(Math.min(currentIndex + 1, totalSlides - 1))});
|
|
25
|
+
flatlistRef.current.scrollToIndex({ index: Math.floor(Math.min(currentIndex + 1, totalSlides - 1)) });
|
|
26
26
|
setCurrentTabIndex(currentIndex + 1);
|
|
27
27
|
}
|
|
28
28
|
};
|
|
@@ -35,9 +35,8 @@ export const StorySlideshow = ({ card, cdn }) => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const showArrow = (direction) => {
|
|
38
|
-
const arrowStyles =
|
|
39
|
-
|
|
40
|
-
const onPressHandler = direction === "left" ? movePrev : moveNext;
|
|
38
|
+
const arrowStyles = direction === 'left' ? styles.leftArrow : styles.rightArrow;
|
|
39
|
+
const onPressHandler = direction === 'left' ? movePrev : moveNext;
|
|
41
40
|
|
|
42
41
|
return (
|
|
43
42
|
<View style={arrowStyles}>
|
|
@@ -53,27 +52,27 @@ export const StorySlideshow = ({ card, cdn }) => {
|
|
|
53
52
|
const contentWidth = event.nativeEvent?.contentSize?.width;
|
|
54
53
|
const layoutWidth = event.nativeEvent?.layoutMeasurement?.width;
|
|
55
54
|
const value = (xOffset / contentWidth);
|
|
56
|
-
const leftThreshold = contentWidth / (
|
|
57
|
-
const rightThreshold = contentWidth - (
|
|
55
|
+
const leftThreshold = contentWidth / (2 * totalSlides);
|
|
56
|
+
const rightThreshold = contentWidth - (1.5) * layoutWidth;
|
|
58
57
|
|
|
59
|
-
if((leftThreshold <= xOffset && (layoutWidth > xOffset || rightThreshold > xOffset
|
|
58
|
+
if ((leftThreshold <= xOffset && (layoutWidth > xOffset || rightThreshold > xOffset))) {
|
|
60
59
|
setCurrentTabIndex(value * totalSlides);
|
|
61
|
-
} else if(xOffset < leftThreshold){
|
|
60
|
+
} else if (xOffset < leftThreshold) {
|
|
62
61
|
setCurrentTabIndex(0);
|
|
63
|
-
} else if(xOffset >= rightThreshold){
|
|
64
|
-
setCurrentTabIndex(totalSlides-1);
|
|
62
|
+
} else if (xOffset >= rightThreshold) {
|
|
63
|
+
setCurrentTabIndex(totalSlides - 1);
|
|
65
64
|
}
|
|
66
|
-
}
|
|
65
|
+
};
|
|
67
66
|
|
|
68
67
|
return (
|
|
69
|
-
<View
|
|
68
|
+
<View>
|
|
70
69
|
<Text primary style={styles.titleText}>
|
|
71
70
|
{card.title}
|
|
72
71
|
</Text>
|
|
73
72
|
|
|
74
73
|
<FlatList
|
|
75
74
|
ref={flatlistRef}
|
|
76
|
-
data={card[
|
|
75
|
+
data={card['story-elements']}
|
|
77
76
|
renderItem={({ item }) => <SlideshowStoryCard card={item} cdn={cdn} />}
|
|
78
77
|
keyExtractor={(item) => item.id}
|
|
79
78
|
horizontal
|
|
@@ -81,9 +80,14 @@ export const StorySlideshow = ({ card, cdn }) => {
|
|
|
81
80
|
onScroll={handleScroll}
|
|
82
81
|
/>
|
|
83
82
|
|
|
84
|
-
{!firstSlide && showArrow(
|
|
83
|
+
{!firstSlide && showArrow('left')}
|
|
85
84
|
|
|
86
|
-
{!lastSlide && showArrow(
|
|
85
|
+
{!lastSlide && showArrow('right')}
|
|
87
86
|
</View>
|
|
88
87
|
);
|
|
89
88
|
};
|
|
89
|
+
|
|
90
|
+
StorySlideshow.propTypes = {
|
|
91
|
+
cdn: PropTypes.string,
|
|
92
|
+
card: PropTypes.object,
|
|
93
|
+
};
|
|
@@ -1,40 +1,36 @@
|
|
|
1
|
-
import { StyleSheet } from
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
2
|
|
|
3
|
-
export const storySlideshowStyles = ({ FONT_SIZE, COLORS }) =>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
alignItems: "center",
|
|
38
|
-
justifyContent: "center",
|
|
39
|
-
},
|
|
40
|
-
});
|
|
3
|
+
export const storySlideshowStyles = ({ FONT_SIZE, COLORS }) => StyleSheet.create({
|
|
4
|
+
titleText: {
|
|
5
|
+
fontWeight: 'normal',
|
|
6
|
+
marginBottom: 10,
|
|
7
|
+
fontSize: FONT_SIZE.h1,
|
|
8
|
+
opacity: 0.8,
|
|
9
|
+
},
|
|
10
|
+
leftArrow: {
|
|
11
|
+
position: 'absolute',
|
|
12
|
+
top: '46%',
|
|
13
|
+
right: '95%',
|
|
14
|
+
left: 20,
|
|
15
|
+
zIndex: 999,
|
|
16
|
+
width: 30,
|
|
17
|
+
height: 40,
|
|
18
|
+
backgroundColor: COLORS.TRANSPARENT_BLACK,
|
|
19
|
+
display: 'flex',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
justifyContent: 'center',
|
|
22
|
+
},
|
|
23
|
+
rightArrow: {
|
|
24
|
+
position: 'absolute',
|
|
25
|
+
top: '46%',
|
|
26
|
+
left: '93%',
|
|
27
|
+
right: 20,
|
|
28
|
+
zIndex: 999,
|
|
29
|
+
width: 30,
|
|
30
|
+
height: 40,
|
|
31
|
+
backgroundColor: COLORS.TRANSPARENT_BLACK,
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
justifyContent: 'center',
|
|
35
|
+
},
|
|
36
|
+
});
|
package/src/utils/imageUtils.js
CHANGED
|
@@ -29,9 +29,11 @@ export function getImageQuality() {
|
|
|
29
29
|
if (NETWORK_TYPE) {
|
|
30
30
|
if (NETWORK_TYPE === 'wifi') {
|
|
31
31
|
return 100;
|
|
32
|
-
}
|
|
32
|
+
}
|
|
33
|
+
if (NETWORK_TYPE === '4g') {
|
|
33
34
|
return 85;
|
|
34
|
-
}
|
|
35
|
+
}
|
|
36
|
+
if (NETWORK_TYPE === '3g') {
|
|
35
37
|
return 50;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -118,3 +120,19 @@ export const getImageURL = (props) => {
|
|
|
118
120
|
q: IMAGE_QUALITY,
|
|
119
121
|
})}`;
|
|
120
122
|
};
|
|
123
|
+
|
|
124
|
+
export const getCustomResolutionImageURL = (props, customAspectRatio) => {
|
|
125
|
+
const { theme } = useContext(AppTheme);
|
|
126
|
+
const { IMAGE_QUALITY } = theme;
|
|
127
|
+
|
|
128
|
+
const {
|
|
129
|
+
metaData, slug, imageWidth, cdn,
|
|
130
|
+
} = props;
|
|
131
|
+
const image = new FocusedImage(slug, metaData);
|
|
132
|
+
|
|
133
|
+
const imageCdn = cdn || 'https://www.quintype.com';
|
|
134
|
+
return `${imageCdn}/${image.path(customAspectRatio, {
|
|
135
|
+
w: getPixelRatioForDevice(imageWidth) || Math.round(deviceWidth),
|
|
136
|
+
q: IMAGE_QUALITY,
|
|
137
|
+
})}`;
|
|
138
|
+
};
|