Skip to content

Commit

Permalink
support playback for ugoira in detail page
Browse files Browse the repository at this point in the history
  • Loading branch information
alphasp committed Oct 18, 2017
1 parent cd071c3 commit 8daf414
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"jsx": true,
"experimentalObjectRestSpread": true
},
"settings": {
"import/resolver": "react-native"
},
"plugins": [
"react",
"react-native",
Expand Down
58 changes: 33 additions & 25 deletions src/components/DetailImageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { connect } from 'react-redux';
import DetailFooter from './DetailFooter';
import PXCacheImageTouchable from './PXCacheImageTouchable';
import UgoiraViewTouchable from './UgoiraViewTouchable';
import PXBottomSheet from './PXBottomSheet';
import PXBottomSheetButton from './PXBottomSheetButton';
import PXBottomSheetCancelButton from './PXBottomSheetCancelButton';
Expand Down Expand Up @@ -257,6 +258,37 @@ class DetailImageList extends Component {
);
};

renderImageOrUgoira = isMute => {
const { item, onPressImage, onLongPressImage } = this.props;
if (isMute) {
return (
<View style={styles.mutedImageContainer}>
<OverlayMutedIndicator />
</View>
);
} else if (item.type === 'ugoira') {
return <UgoiraViewTouchable item={item} />;
}
return (
<PXCacheImageTouchable
uri={item.image_urls.medium}
initWidth={
item.width > globalStyleVariables.WINDOW_WIDTH
? globalStyleVariables.WINDOW_WIDTH
: item.width
}
initHeight={
globalStyleVariables.WINDOW_WIDTH * item.height / item.width
}
style={styles.imageContainer}
imageStyle={styles.image}
onPress={onPressImage}
onLongPress={onLongPressImage}
index={0}
/>
);
};

renderFooter = () => {
const { item, navigation, i18n, authUser, tags } = this.props;
return (
Expand Down Expand Up @@ -284,8 +316,6 @@ class DetailImageList extends Component {
highlightTags,
muteTags,
isMuteUser,
onPressImage,
onLongPressImage,
} = this.props;
const {
imagePageNumber,
Expand Down Expand Up @@ -323,29 +353,7 @@ class DetailImageList extends Component {
scrollEventThrottle={16}
bounces={false}
>
{isMute
? <View style={styles.mutedImageContainer}>
<OverlayMutedIndicator />
</View>
: <PXCacheImageTouchable
uri={item.image_urls.medium}
initWidth={
item.width > globalStyleVariables.WINDOW_WIDTH
? globalStyleVariables.WINDOW_WIDTH
: item.width
}
initHeight={
globalStyleVariables.WINDOW_WIDTH *
item.height /
item.width
}
style={styles.imageContainer}
imageStyle={styles.image}
onPress={onPressImage}
onLongPress={onLongPressImage}
index={0}
/>}

{this.renderImageOrUgoira(isMute)}
{this.renderFooter()}
</ScrollView>}
<PXBottomSheet
Expand Down
199 changes: 199 additions & 0 deletions src/components/UgoiraViewTouchable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React, { Component } from 'react';
import {
View,
Platform,
TouchableWithoutFeedback,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
import RNFetchBlob from 'react-native-fetch-blob';
import { unzip } from 'react-native-zip-archive';
import UgoiraView from './UgoiraView';
import PXCacheImage from './PXCacheImage';
import Loader from './Loader';
import OverlayPlayIcon from './OverlayPlayIcon';
import * as ugoiraMetaActionCreators from '../common/actions/ugoiraMeta';
import { globalStyleVariables } from '../styles';

const styles = StyleSheet.create({
imageContainer: {
backgroundColor: globalStyleVariables.BACKGROUND_COLOR,
justifyContent: 'center',
alignItems: 'center',
},
image: {
resizeMode: 'contain',
},
});

class UgoiraViewTouchable extends Component {
constructor(props) {
super(props);
this.state = {
ugoiraPath: null,
zipPath: null,
isDownloadingZip: false,
isStartPlaying: false,
};
}

componentDidUpdate(prevProps) {
const { ugoiraMeta: prevUgoiraMeta } = prevProps;
const { ugoiraMeta } = this.props;
if (
prevUgoiraMeta &&
ugoiraMeta &&
ugoiraMeta.loaded &&
ugoiraMeta.loaded !== prevUgoiraMeta.loaded &&
ugoiraMeta.item &&
ugoiraMeta.item.zipUrl &&
ugoiraMeta.item.frames &&
ugoiraMeta.item.frames.length
) {
this.downloadZip();
}
}

downloadZip = async () => {
const { item: { id }, ugoiraMeta } = this.props;
const { zipUrl } = ugoiraMeta.item;
try {
const ugoiraPath = `${RNFetchBlob.fs.dirs.CacheDir}/pxview/ugoira/${id}`;
const isDir = await RNFetchBlob.fs.isDir(ugoiraPath);
if (isDir) {
if (!this.unmounting) {
this.setState({
ugoiraPath,
});
}
} else {
const downloadPath = `${RNFetchBlob.fs.dirs
.CacheDir}/pxview/ugoira_zip/${zipUrl.split('/').pop()}`;
this.task = RNFetchBlob.config({
fileCache: true,
appendExt: 'zip',
key: zipUrl,
path: downloadPath,
}).fetch('GET', zipUrl, {
referer: 'http://www.pixiv.net',
});
try {
this.setState({
isDownloadingZip: true,
});
const res = await this.task;
if (!this.unmounting) {
try {
const path = await unzip(res.path(), ugoiraPath);
this.setState({
ugoiraPath: path,
isDownloadingZip: false,
});
// remove zip file after extracted
res.flush();
} catch (err) {
this.setState({
isDownloadingZip: false,
});
}
}
} catch (err) {
this.setState({
isDownloadingZip: false,
});
}
}
} catch (err) {
// console.error('failed to call isDir ', err);
}
};

componentWillUnmount() {
this.unmounting = true;
if (this.task) {
this.task.cancel();
}
}

fetchUgoiraMetaOrToggleAnimation = () => {
const { item, ugoiraMeta, fetchUgoiraMeta } = this.props;
const { ugoiraPath, isStartPlaying } = this.state;
if (!isStartPlaying) {
this.setState({
isStartPlaying: true,
});
}
if (!ugoiraMeta || !ugoiraMeta.loaded) {
fetchUgoiraMeta(item.id);
} else {
const { zipUrl, frames } = ugoiraMeta.item;
if (ugoiraPath) {
if (zipUrl && frames && frames.length) {
this.setState(state => ({
paused: !state.paused,
}));
}
} else {
// when ugoiraMeta is cached
this.downloadZip();
}
}
};

render() {
const { item, ugoiraMeta } = this.props;
const { ugoiraPath, isDownloadingZip, isStartPlaying, paused } = this.state;
const width =
item.width > globalStyleVariables.WINDOW_WIDTH
? globalStyleVariables.WINDOW_WIDTH
: item.width;
const height = Math.floor(
globalStyleVariables.WINDOW_WIDTH * item.height / item.width,
);
return (
<TouchableWithoutFeedback onPress={this.fetchUgoiraMetaOrToggleAnimation}>
<View
style={[
styles.imageContainer,
{
width: globalStyleVariables.WINDOW_WIDTH,
height,
},
]}
>
{ugoiraPath
? <UgoiraView
images={ugoiraMeta.item.frames.map(frame => ({
uri:
Platform.OS === 'android'
? `file://${ugoiraPath}/${frame.file}`
: `${ugoiraPath}/${frame.file}`,
delay: frame.delay,
}))}
paused={paused}
width={width}
height={height}
resizeMode="contain"
/>
: <PXCacheImage
uri={item.image_urls.medium}
width={width}
height={height}
style={styles.image}
/>}
{((ugoiraMeta && ugoiraMeta.loading) || isDownloadingZip) &&
<Loader absolutePosition />}
{!isStartPlaying && <OverlayPlayIcon />}
</View>
</TouchableWithoutFeedback>
);
}
}

export default connect((state, props) => {
const { ugoiraMeta } = state;
const { item } = props;
return {
ugoiraMeta: ugoiraMeta[item.id],
};
}, ugoiraMetaActionCreators)(UgoiraViewTouchable);
6 changes: 3 additions & 3 deletions src/styles/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export const BACKGROUND_COLOR = '#E9EBEE';
export const HIGHLIGHT_COLOR = 'green';
export const MUTE_COLOR = 'red';

export const WINDOW_WIDTH = Dimensions.get('window').width;
export const WINDOW_HEIGHT = Dimensions.get('window').height;
export const WINDOW_WIDTH = Math.floor(Dimensions.get('window').width);
export const WINDOW_HEIGHT = Math.floor(Dimensions.get('window').height);

export const APPBAR_HEIGHT = Platform.OS === 'ios' ? 44 : 56;
export const STATUSBAR_HEIGHT = Platform.OS === 'ios' ? 20 : 0;
export const DRAWER_WIDTH =
Dimensions.get('window').width - (Platform.OS === 'android' ? 56 : 64);
WINDOW_WIDTH - (Platform.OS === 'android' ? 56 : 64);

0 comments on commit 8daf414

Please sign in to comment.