Merge branch 'sa/post-video-render' of https://github.com/ecency/ecency-mobile into sa/post-video-render

This commit is contained in:
noumantahir 2022-01-23 00:26:55 +05:00
commit e8d0f67619
4 changed files with 310 additions and 247 deletions

View File

@ -93,6 +93,7 @@ import { ForegroundNotification } from './foregroundNotification';
import { PostHtmlRenderer } from './postHtmlRenderer'; import { PostHtmlRenderer } from './postHtmlRenderer';
import { QuickProfileModal } from './organisms'; import { QuickProfileModal } from './organisms';
import QuickReplyModal from './quickReplyModal/quickReplyModalView'; import QuickReplyModal from './quickReplyModal/quickReplyModalView';
import VideoPlayer from './videoPlayer/videoPlayerView';
// Basic UI Elements // Basic UI Elements
import { import {
@ -234,4 +235,5 @@ export {
PostHtmlRenderer, PostHtmlRenderer,
QuickProfileModal, QuickProfileModal,
QuickReplyModal, QuickReplyModal,
VideoPlayer,
}; };

View File

@ -1,27 +1,31 @@
import React, { memo, } from "react"; import React, { memo } from 'react';
import RenderHTML, { CustomRendererProps, Element, TNode } from "react-native-render-html"; import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html';
import styles from "./postHtmlRendererStyles"; import styles from './postHtmlRendererStyles';
import { LinkData, parseLinkData } from "./linkDataParser"; import { LinkData, parseLinkData } from './linkDataParser';
import VideoThumb from "./videoThumb"; import VideoThumb from './videoThumb';
import { AutoHeightImage } from "../autoHeightImage/autoHeightImage"; import { AutoHeightImage } from '../autoHeightImage/autoHeightImage';
import { useHtmlIframeProps,iframeModel } from '@native-html/iframe-plugin'; import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin';
import WebView from "react-native-webview"; import WebView from 'react-native-webview';
import { View } from 'react-native';
import YoutubeIframe from 'react-native-youtube-iframe';
import { VideoPlayer } from '..';
interface PostHtmlRendererProps { interface PostHtmlRendererProps {
contentWidth:number; contentWidth: number;
body:string; body: string;
onLoaded?:()=>void; onLoaded?: () => void;
setSelectedImage:(imgUrl:string)=>void; setSelectedImage: (imgUrl: string) => void;
setSelectedLink:(url:string)=>void; setSelectedLink: (url: string) => void;
onElementIsImage:(imgUrl:string)=>void; onElementIsImage: (imgUrl: string) => void;
handleOnPostPress:(permlink:string, authro:string)=>void; handleOnPostPress: (permlink: string, authro: string) => void;
handleOnUserPress:(username:string)=>void; handleOnUserPress: (username: string) => void;
handleTagPress:(tag:string, filter?:string)=>void; handleTagPress: (tag: string, filter?: string) => void;
handleVideoPress:(videoUrl:string)=>void; handleVideoPress: (videoUrl: string) => void;
handleYoutubePress:(videoId:string, startTime:number)=>void; handleYoutubePress: (videoId: string, startTime: number) => void;
} }
export const PostHtmlRenderer = memo(({ export const PostHtmlRenderer = memo(
({
contentWidth, contentWidth,
body, body,
onLoaded, onLoaded,
@ -33,209 +37,174 @@ export const PostHtmlRenderer = memo(({
handleTagPress, handleTagPress,
handleVideoPress, handleVideoPress,
handleYoutubePress, handleYoutubePress,
}:PostHtmlRendererProps) => { }: PostHtmlRendererProps) => {
//new renderer functions
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g, '</div>');
//new renderer functions console.log('Comment body:', body);
body = body.replace(/<center>/g, '<div class="text-center">').replace(/<\/center>/g,'</div>');
console.log("Comment body:", body); const _handleOnLinkPress = (data: LinkData) => {
if (!data) {
const _handleOnLinkPress = (data:LinkData) => { return;
if(!data){
return;
}
const {
type,
href,
author,
permlink,
tag,
youtubeId,
startTime,
filter,
videoHref,
community
} = data;
try {
switch (type) {
case '_external':
case 'markdown-external-link':
setSelectedLink(href);
break;
case 'markdown-author-link':
if (handleOnUserPress) {
handleOnUserPress(author);
}
break;
case 'markdown-post-link':
if (handleOnPostPress) {
handleOnPostPress(permlink, author);
}
break;
case 'markdown-tag-link':
if(handleTagPress){
handleTagPress(tag, filter);
}
break;
case 'markdown-video-link':
if(handleVideoPress){
handleVideoPress(videoHref)
}
break;
case 'markdown-video-link-youtube':
if(handleYoutubePress){
handleYoutubePress(youtubeId, startTime)
}
break;
//unused cases
case 'markdown-witnesses-link':
setSelectedLink(href);
break;
case 'markdown-proposal-link':
setSelectedLink(href);
break;
case 'markdown-community-link':
//tag press also handles community by default
if(handleTagPress){
handleTagPress(community, filter)
}
break;
default:
break;
} }
} catch (error) {}
}; const {
type,
href,
const _onElement = (element:Element) => { author,
if(element.tagName === 'img' && element.attribs.src){ permlink,
tag,
youtubeId,
startTime,
filter,
videoHref,
community,
} = data;
try {
switch (type) {
case '_external':
case 'markdown-external-link':
setSelectedLink(href);
break;
case 'markdown-author-link':
if (handleOnUserPress) {
handleOnUserPress(author);
}
break;
case 'markdown-post-link':
if (handleOnPostPress) {
handleOnPostPress(permlink, author);
}
break;
case 'markdown-tag-link':
if (handleTagPress) {
handleTagPress(tag, filter);
}
break;
case 'markdown-video-link':
if (handleVideoPress) {
handleVideoPress(videoHref);
}
break;
case 'markdown-video-link-youtube':
if (handleYoutubePress) {
handleYoutubePress(youtubeId, startTime);
}
break;
//unused cases
case 'markdown-witnesses-link':
setSelectedLink(href);
break;
case 'markdown-proposal-link':
setSelectedLink(href);
break;
case 'markdown-community-link':
//tag press also handles community by default
if (handleTagPress) {
handleTagPress(community, filter);
}
break;
default:
break;
}
} catch (error) {}
};
const _onElement = (element: Element) => {
if (element.tagName === 'img' && element.attribs.src) {
const imgUrl = element.attribs.src; const imgUrl = element.attribs.src;
console.log("img element detected", imgUrl); console.log('img element detected', imgUrl);
onElementIsImage(imgUrl) onElementIsImage(imgUrl);
} }
}; };
const _anchorRenderer = ({ InternalRenderer, tnode, ...props }: CustomRendererProps<TNode>) => {
const _anchorRenderer = ({ const parsedTnode = parseLinkData(tnode);
InternalRenderer,
tnode,
...props
}:CustomRendererProps<TNode>) => {
const _onPress = () => { const _onPress = () => {
console.log("Link Pressed:", tnode) console.log('Link Pressed:', tnode);
const data = parseLinkData(tnode); const data = parseLinkData(tnode);
_handleOnLinkPress(data); _handleOnLinkPress(data);
}; };
if (tnode.classes?.indexOf('markdown-video-link') >= 0) { if (tnode.classes?.indexOf('markdown-video-link-youtube') >= 0) {
// get video src
let videoHref = tnode.attributes['data-embed-src'] || tnode.attributes['data-video-href'] || tnode.children[0].attributes['src'];
return ( return (
<WebView <VideoPlayer
scalesPageToFit={true} contentWidth={contentWidth}
bounces={false} youtubeVideoId={parsedTnode.youtubeId}
javaScriptEnabled={true} startTime={parsedTnode.startTime}
automaticallyAdjustContentInsets={false}
onLoadEnd={() => {
console.log('load end');
}}
onLoadStart={() => {
console.log('load start');
}}
source={{ uri: videoHref }}
style={{ width: contentWidth, height: (contentWidth * 9) / 16 }}
startInLoadingState={true}
onShouldStartLoadWithRequest={() => true}
mediaPlaybackRequiresUserAction={true}
allowsInlineMediaPlayback={true}
/> />
); );
} }
if(tnode.classes?.indexOf('markdown-video-link') >= 0){ if (tnode.classes?.indexOf('markdown-video-link') >= 0) {
const imgElement = tnode.children.find((child)=>{ return (
return child.classes.indexOf('video-thumbnail') > 0 ? true:false <VideoPlayer
}) contentWidth={contentWidth}
if(!imgElement){ videoUrl={parsedTnode.videoHref}
return ( />
<VideoThumb contentWidth={contentWidth} onPress={_onPress} /> );
) }
if (tnode.classes?.indexOf('markdown-video-link') >= 0) {
const imgElement = tnode.children.find((child) => {
return child.classes.indexOf('video-thumbnail') > 0 ? true : false;
});
if (!imgElement) {
return <VideoThumb contentWidth={contentWidth} onPress={_onPress} />;
} }
} }
return <InternalRenderer tnode={tnode} onPress={_onPress} {...props} />;
return ( };
<InternalRenderer
tnode={tnode}
onPress={_onPress}
{...props}
/>
)
}
//this method checks if image is a child of table column //this method checks if image is a child of table column
//and calculates img width accordingly, //and calculates img width accordingly,
//returns full width if img is not part of table //returns full width if img is not part of table
const getMaxImageWidth = (tnode:TNode)=>{ const getMaxImageWidth = (tnode: TNode) => {
//return full width if not parent exist //return full width if not parent exist
if(!tnode.parent || tnode.parent.tagName === 'body'){ if (!tnode.parent || tnode.parent.tagName === 'body') {
return contentWidth; return contentWidth;
} }
//return divided width based on number td tags //return divided width based on number td tags
if(tnode.parent.tagName === 'td'){ if (tnode.parent.tagName === 'td') {
const cols = tnode.parent.parent.children.length const cols = tnode.parent.parent.children.length;
return contentWidth/cols; return contentWidth / cols;
} }
//check next parent //check next parent
return getMaxImageWidth(tnode.parent); return getMaxImageWidth(tnode.parent);
} };
const _imageRenderer = ({ tnode }: CustomRendererProps<TNode>) => {
const _imageRenderer = ({
tnode,
}:CustomRendererProps<TNode>) => {
const imgUrl = tnode.attributes.src; const imgUrl = tnode.attributes.src;
const _onPress = () => { const _onPress = () => {
console.log("Image Pressed:", imgUrl) console.log('Image Pressed:', imgUrl);
setSelectedImage(imgUrl); setSelectedImage(imgUrl);
}; };
const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0; const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0;
const isAnchored = tnode.parent?.tagName === 'a'; const isAnchored = tnode.parent?.tagName === 'a';
if(isVideoThumb){ if (isVideoThumb) {
return <VideoThumb contentWidth={contentWidth} uri={imgUrl}/>; return <VideoThumb contentWidth={contentWidth} uri={imgUrl} />;
} } else {
else {
const maxImgWidth = getMaxImageWidth(tnode); const maxImgWidth = getMaxImageWidth(tnode);
return ( return (
<AutoHeightImage <AutoHeightImage
contentWidth={maxImgWidth} contentWidth={maxImgWidth}
imgUrl={imgUrl} imgUrl={imgUrl}
isAnchored={isAnchored} isAnchored={isAnchored}
onPress={_onPress} onPress={_onPress}
/> />
) );
} }
};
}
/** /**
* the para renderer is designd to remove margins from para * the para renderer is designd to remove margins from para
@ -243,31 +212,18 @@ export const PostHtmlRenderer = memo(({
* a weired misalignment of bullet and content * a weired misalignment of bullet and content
* @returns Default Renderer * @returns Default Renderer
*/ */
const _paraRenderer = ({ const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps<TNode>) => {
TDefaultRenderer, props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p;
...props
}:CustomRendererProps<TNode>) => {
props.style = props.tnode.parent.tagName === 'li' return <TDefaultRenderer {...props} />;
? styles.pLi };
: styles.p
return (
<TDefaultRenderer
{...props}
/>
)
}
// iframe renderer for rendering iframes in body // iframe renderer for rendering iframes in body
const _iframeRenderer = function IframeRenderer(props) { const _iframeRenderer = function IframeRenderer(props) {
const iframeProps = useHtmlIframeProps(props); const iframeProps = useHtmlIframeProps(props);
// console.log('iframeProps : ', iframeProps);
const checkSrcRegex = /(.*?)\.(mp4|webm|ogg)$/gi; const checkSrcRegex = /(.*?)\.(mp4|webm|ogg)$/gi;
const isVideoType = iframeProps.source.uri.match(checkSrcRegex); const isVideoType = iframeProps.source.uri.match(checkSrcRegex);
// check if source contain video source then wrap it with video tag
// else pass the source directly to webview
const src = isVideoType const src = isVideoType
? { ? {
html: ` html: `
@ -300,55 +256,57 @@ export const PostHtmlRenderer = memo(({
/> />
); );
}; };
return ( return (
<RenderHTML <RenderHTML
source={{ html:body }} source={{ html: body }}
contentWidth={contentWidth} contentWidth={contentWidth}
baseStyle={{...styles.baseStyle, width:contentWidth}} baseStyle={{ ...styles.baseStyle, width: contentWidth }}
classesStyles={{ classesStyles={{
phishy:styles.phishy, phishy: styles.phishy,
'text-justify':styles.textJustify, 'text-justify': styles.textJustify,
'text-center':styles.textCenter 'text-center': styles.textCenter,
}} }}
tagsStyles={{ tagsStyles={{
body:styles.body, body: styles.body,
a:styles.a, a: styles.a,
img:styles.img, img: styles.img,
th:styles.th, th: styles.th,
tr:{...styles.tr, width:contentWidth}, //center tag causes tr to have 0 width if not exclusivly set, contentWidth help avoid that tr: { ...styles.tr, width: contentWidth }, //center tag causes tr to have 0 width if not exclusivly set, contentWidth help avoid that
td:styles.td, td: styles.td,
blockquote:styles.blockquote, blockquote: styles.blockquote,
code:styles.code, code: styles.code,
li:styles.li, li: styles.li,
p:styles.p, p: styles.p,
table:styles.table, table: styles.table,
}} }}
domVisitors={{ domVisitors={{
onElement:_onElement onElement: _onElement,
}} }}
renderers={{ renderers={{
img:_imageRenderer, img: _imageRenderer,
a:_anchorRenderer, a: _anchorRenderer,
p:_paraRenderer, p: _paraRenderer,
iframe: _iframeRenderer, iframe: _iframeRenderer,
}} }}
onHTMLLoaded={onLoaded && onLoaded} onHTMLLoaded={onLoaded && onLoaded}
defaultTextProps={{ defaultTextProps={{
selectable:true selectable: true,
}} }}
customHTMLElementModels={{ customHTMLElementModels={{
iframe: iframeModel iframe: iframeModel,
}} }}
renderersProps={{ renderersProps={{
iframe: { iframe: {
scalesPageToFit: true, scalesPageToFit: true,
webViewProps: { webViewProps: {
/* Any prop you want to pass to iframe WebViews */ /* Any prop you want to pass to iframe WebViews */
} },
} },
}} }}
WebView={WebView} WebView={WebView}
/> />
) );
}, (next, prev)=>next.body === prev.body) },
(next, prev) => next.body === prev.body,
);

View File

@ -0,0 +1,5 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
});

View File

@ -0,0 +1,98 @@
import React, { useState } from 'react';
import style from './videoPlayerStyles';
import { Dimensions } from 'react-native';
import { View, StyleSheet, ActivityIndicator } from 'react-native';
import WebView from 'react-native-webview';
import YoutubeIframe, { InitialPlayerParams } from 'react-native-youtube-iframe';
interface VideoPlayerProps {
youtubeVideoId?: string;
videoUrl?: string;
startTime?: number;
contentWidth?: number;
}
const VideoPlayer = ({ youtubeVideoId, videoUrl, startTime, contentWidth }: VideoPlayerProps) => {
const PLAYER_HEIGHT = Dimensions.get('screen').width * (9 / 16);
const [shouldPlay, setShouldPlay] = useState(false);
const [loading, setLoading] = useState(true);
const _onReady = () => {
setLoading(false);
setShouldPlay(true);
console.log('ready');
};
const _onChangeState = (event: string) => {
console.log(event);
setShouldPlay(!(event == 'paused' || event == 'ended'));
};
const _onError = () => {
console.log('error!');
setLoading(false);
};
const initialParams: InitialPlayerParams = {
start: startTime,
};
return (
<View style={styles.container}>
{youtubeVideoId && (
<View style={{ width: contentWidth, height: PLAYER_HEIGHT }}>
<YoutubeIframe
height={PLAYER_HEIGHT}
videoId={youtubeVideoId}
initialPlayerParams={initialParams}
onReady={_onReady}
play={shouldPlay}
onChangeState={_onChangeState}
onError={_onError}
/>
</View>
)}
{videoUrl && (
<View style={{ height: PLAYER_HEIGHT }}>
<WebView
scalesPageToFit={true}
bounces={false}
javaScriptEnabled={true}
automaticallyAdjustContentInsets={false}
onLoadEnd={() => {
setLoading(false);
}}
onLoadStart={() => {
setLoading(true);
}}
source={{ uri: videoUrl }}
style={{ width: contentWidth, height: (contentWidth * 9) / 16 }}
startInLoadingState={true}
onShouldStartLoadWithRequest={() => true}
mediaPlaybackRequiresUserAction={true}
allowsInlineMediaPlayback={true}
/>
</View>
)}
{loading && <ActivityIndicator style={styles.activityIndicator} />}
</View>
);
};
export default VideoPlayer;
const styles = StyleSheet.create({
container: {
paddingVertical: 16,
},
activityIndicator: {
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
top: 0,
bottom: 0,
left: 0,
right: 0,
},
});