From deb9771b68c42bd9ef84b2150df7266f881aacbc Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 18 Mar 2018 15:42:16 +0100 Subject: [PATCH] now with download progress bar and media playback --- app/package.json | 1 + app/src/component/fileDownloadButton/index.js | 23 + app/src/component/fileDownloadButton/view.js | 80 ++ app/src/page/file/view.js | 49 +- app/src/redux/actions/file.js | 222 ++++ app/src/styles/fileDownloadButton.js | 18 + app/src/styles/filePage.js | 11 +- .../bootstraps/lbry/build/build.py | 13 +- .../ic_file_download_black_24dp.png | Bin 0 -> 148 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 114 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 144 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 173 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 209 bytes .../lbry/build/templates/build.tmpl.gradle | 1 + .../ic_file_download_black_24dp.png | Bin 0 -> 148 bytes .../res/drawable-hdpi/ic_launcher.png | Bin 0 -> 2683 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 114 bytes .../res/drawable-mdpi/ic_launcher.png | Bin 0 -> 1698 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 144 bytes .../res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 3872 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 173 bytes .../res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 6874 bytes .../ic_file_download_black_24dp.png | Bin 0 -> 209 bytes .../lbry/build/templates/settings.tmpl.gradle | 3 + package-lock.json | 3 + src/main/assets/index.android.bundle | 1037 ++++++++++++++++- src/main/assets/index.android.bundle.meta | 2 +- .../java/io/lbry/lbrynet/MainActivity.java | 7 +- .../LbryDownloadManagerModule.java | 99 ++ .../reactpackages/LbryReactPackage.java | 28 + 30 files changed, 1530 insertions(+), 67 deletions(-) create mode 100644 app/src/component/fileDownloadButton/index.js create mode 100644 app/src/component/fileDownloadButton/view.js create mode 100644 app/src/redux/actions/file.js create mode 100644 app/src/styles/fileDownloadButton.js create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png create mode 100644 p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle create mode 100644 package-lock.json create mode 100644 src/main/java/io/lbry/lbrynet/reactmodules/LbryDownloadManagerModule.java create mode 100644 src/main/java/io/lbry/lbrynet/reactpackages/LbryReactPackage.java diff --git a/app/package.json b/app/package.json index 8f72fc2f..3eda28f5 100644 --- a/app/package.json +++ b/app/package.json @@ -11,6 +11,7 @@ "react": "16.2.0", "react-native": "0.52.0", "react-native-vector-icons": "^4.5.0", + "react-native-video": "2.0.0", "react-navigation": "^1.0.3", "react-navigation-redux-helpers": "^1.0.1", "react-redux": "^5.0.3", diff --git a/app/src/component/fileDownloadButton/index.js b/app/src/component/fileDownloadButton/index.js new file mode 100644 index 00000000..ee81d2d6 --- /dev/null +++ b/app/src/component/fileDownloadButton/index.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux'; +import { + makeSelectFileInfoForUri, + makeSelectDownloadingForUri, + makeSelectLoadingForUri, + makeSelectCostInfoForUri +} from 'lbry-redux'; +import { doPurchaseUri, doStartDownload } from '../../redux/actions/file'; +import FileDownloadButton from './view'; + +const select = (state, props) => ({ + fileInfo: makeSelectFileInfoForUri(props.uri)(state), + downloading: makeSelectDownloadingForUri(props.uri)(state), + costInfo: makeSelectCostInfoForUri(props.uri)(state), + loading: makeSelectLoadingForUri(props.uri)(state), +}); + +const perform = dispatch => ({ + purchaseUri: uri => dispatch(doPurchaseUri(uri)), + restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)) +}); + +export default connect(select, perform)(FileDownloadButton); \ No newline at end of file diff --git a/app/src/component/fileDownloadButton/view.js b/app/src/component/fileDownloadButton/view.js new file mode 100644 index 00000000..cccf2841 --- /dev/null +++ b/app/src/component/fileDownloadButton/view.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { Text, View, TouchableOpacity } from 'react-native'; +import fileDownloadButtonStyle from '../../styles/fileDownloadButton'; + +class FileDownloadButton extends React.PureComponent { + componentWillReceiveProps(nextProps) { + //this.checkAvailability(nextProps.uri); + this.restartDownload(nextProps); + } + + restartDownload(props) { + const { downloading, fileInfo, uri, restartDownload } = props; + + if ( + !downloading && + fileInfo && + !fileInfo.completed && + fileInfo.written_bytes !== false && + fileInfo.written_bytes < fileInfo.total_bytes + ) { + restartDownload(uri, fileInfo.outpoint); + } + } + + render() { + const { + fileInfo, + downloading, + uri, + purchaseUri, + costInfo, + loading, + doPause, + style, + } = this.props; + + const openFile = () => { + //openInShell(fileInfo.download_path); + //doPause(); + }; + + if (loading || downloading) { + const progress = + fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, + label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...'; + + return ( + + + {label} + + ); + } else if (fileInfo === null && !downloading) { + if (!costInfo) { + return ( + + Fetching cost info... + + ); + } + return ( + { + purchaseUri(uri); + }}> + Download + + ); + } else if (fileInfo && fileInfo.download_path) { + return ( + openFile()}> + Open + + ); + } + + return null; + } +} + +export default FileDownloadButton; diff --git a/app/src/page/file/view.js b/app/src/page/file/view.js index bad1700e..956ee184 100644 --- a/app/src/page/file/view.js +++ b/app/src/page/file/view.js @@ -1,9 +1,22 @@ import React from 'react'; -import { Text, View, ScrollView } from 'react-native'; +import { Lbry } from 'lbry-redux'; +import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; +import Video from 'react-native-video'; import filePageStyle from '../../styles/filePage'; import FileItemMedia from '../../component/fileItemMedia'; +import FileDownloadButton from '../../component/fileDownloadButton'; class FilePage extends React.PureComponent { + state = { + rate: 1, + volume: 1, + muted: false, + resizeMode: 'contain', + duration: 0.0, + currentTime: 0.0, + paused: true, + }; + static navigationOptions = { title: '' }; @@ -28,7 +41,7 @@ class FilePage extends React.PureComponent { props.fetchCostInfo(props.navigation.state.params.uri); } } - + render() { const { claim, @@ -36,9 +49,9 @@ class FilePage extends React.PureComponent { metadata, contentType, tab, - uri, rewardedContentClaimIds, - } = this.props; + navigation + } = this.props; if (!claim || !metadata) { return ( @@ -48,23 +61,37 @@ class FilePage extends React.PureComponent { ); } + const completed = fileInfo && fileInfo.completed; const title = metadata.title; const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id); const description = metadata.description ? metadata.description : null; - //const mediaType = lbry.getMediaType(contentType); - //const player = require('render-media'); - //const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; - /*const isPlayable = - Object.values(player.mime).indexOf(contentType) !== -1 || mediaType === 'audio';*/ + const mediaType = Lbry.getMediaType(contentType); + const isPlayable = mediaType === 'video' || mediaType === 'audio'; const { height, channel_name: channelName, value } = claim; const channelClaimId = value && value.publisherSignature && value.publisherSignature.certificateId; - return ( - + {(!fileInfo || !isPlayable) && } + {!completed && } + + {fileInfo && isPlayable && + this.setState({ paused: !this.state.paused })}> + + } + {title} diff --git a/app/src/redux/actions/file.js b/app/src/redux/actions/file.js new file mode 100644 index 00000000..4c91820c --- /dev/null +++ b/app/src/redux/actions/file.js @@ -0,0 +1,222 @@ +import { + ACTIONS, + Lbry, + makeSelectCostInfoForUri, + makeSelectFileInfoForUri, + selectTotalDownloadProgress, + selectDownloadingByOutpoint, +} from 'lbry-redux'; +import { NativeModules } from 'react-native'; + +const DOWNLOAD_POLL_INTERVAL = 250; + +export function doUpdateLoadStatus(uri, outpoint) { + return (dispatch, getState) => { + Lbry.file_list({ + outpoint, + full_status: true, + }).then(([fileInfo]) => { + if (!fileInfo || fileInfo.written_bytes === 0) { + // download hasn't started yet + setTimeout(() => { + dispatch(doUpdateLoadStatus(uri, outpoint)); + }, DOWNLOAD_POLL_INTERVAL); + } else if (fileInfo.completed) { + // TODO this isn't going to get called if they reload the client before + // the download finished + const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo; + dispatch({ + type: ACTIONS.DOWNLOADING_COMPLETED, + data: { + uri, + outpoint, + fileInfo, + }, + }); + + NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, 100, writtenBytes, totalBytes); + + /*const notif = new window.Notification('LBRY Download Complete', { + body: fileInfo.metadata.stream.metadata.title, + silent: false, + }); + notif.onclick = () => { + ipcRenderer.send('focusWindow', 'main'); + };*/ + } else { + // ready to play + const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo; + const progress = writtenBytes / totalBytes * 100; + + dispatch({ + type: ACTIONS.DOWNLOADING_PROGRESSED, + data: { + uri, + outpoint, + fileInfo, + progress, + }, + }); + + NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, progress, writtenBytes, totalBytes); + + setTimeout(() => { + dispatch(doUpdateLoadStatus(uri, outpoint)); + }, DOWNLOAD_POLL_INTERVAL); + } + }); + }; +} + +export function doStartDownload(uri, outpoint) { + return (dispatch, getState) => { + const state = getState(); + + if (!outpoint) { + throw new Error('outpoint is required to begin a download'); + } + + const { downloadingByOutpoint = {} } = state.fileInfo; + + if (downloadingByOutpoint[outpoint]) return; + + Lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => { + dispatch({ + type: ACTIONS.DOWNLOADING_STARTED, + data: { + uri, + outpoint, + fileInfo, + }, + }); + + NativeModules.LbryDownloadManager.startDownload(uri, fileInfo.file_name); + + dispatch(doUpdateLoadStatus(uri, outpoint)); + }); + }; +} + +export function doDownloadFile(uri, streamInfo) { + return dispatch => { + dispatch(doStartDownload(uri, streamInfo.outpoint)); + + //analytics.apiLog(uri, streamInfo.output, streamInfo.claim_id); + + //dispatch(doClaimEligiblePurchaseRewards()); + }; +} + +export function doSetPlayingUri(uri) { + return dispatch => { + dispatch({ + type: ACTIONS.SET_PLAYING_URI, + data: { uri }, + }); + }; +} + +export function doLoadVideo(uri) { + return dispatch => { + dispatch({ + type: ACTIONS.LOADING_VIDEO_STARTED, + data: { + uri, + }, + }); + + Lbry.get({ uri }) + .then(streamInfo => { + const timeout = + streamInfo === null || typeof streamInfo !== 'object' || streamInfo.error === 'Timeout'; + + if (timeout) { + dispatch(doSetPlayingUri(null)); + dispatch({ + type: ACTIONS.LOADING_VIDEO_FAILED, + data: { uri }, + }); + + console.log(`File timeout for uri ${uri}`); + //dispatch(doOpenModal(MODALS.FILE_TIMEOUT, { uri })); + } else { + dispatch(doDownloadFile(uri, streamInfo)); + } + }) + .catch(() => { + dispatch(doSetPlayingUri(null)); + dispatch({ + type: ACTIONS.LOADING_VIDEO_FAILED, + data: { uri }, + }); + + console.log(`Failed to download ${uri}`); + /*dispatch( + doAlertError( + `Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.` + ) + );*/ + }); + }; +} + +export function doPurchaseUri(uri, specificCostInfo) { + return (dispatch, getState) => { + const state = getState(); + const balance = 0;//selectBalance(state); + const fileInfo = makeSelectFileInfoForUri(uri)(state); + const downloadingByOutpoint = selectDownloadingByOutpoint(state); + const alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; + + function attemptPlay(cost, instantPurchaseMax = null) { + if (cost > 0 && (!instantPurchaseMax || cost > instantPurchaseMax)) { + //dispatch(doOpenModal(MODALS.AFFIRM_PURCHASE, { uri })); + console.log('Affirm purchase...'); + } else { + dispatch(doLoadVideo(uri)); + } + } + + // we already fully downloaded the file. + if (fileInfo && fileInfo.completed) { + // If written_bytes is false that means the user has deleted/moved the + // file manually on their file system, so we need to dispatch a + // doLoadVideo action to reconstruct the file from the blobs + if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri)); + + Promise.resolve(); + return; + } + + // we are already downloading the file + if (alreadyDownloading) { + Promise.resolve(); + return; + } + + const costInfo = makeSelectCostInfoForUri(uri)(state) || specificCostInfo; + const { cost } = costInfo; + + if (cost > balance) { + dispatch(doSetPlayingUri(null)); + //dispatch(doOpenModal(MODALS.INSUFFICIENT_CREDITS)); + Promise.resolve(); + return; + } + + if (cost === 0/* || !makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state)*/) { + attemptPlay(cost); + } + /*} else { + const instantPurchaseMax = makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state); + if (instantPurchaseMax.currency === 'LBC') { + attemptPlay(cost, instantPurchaseMax.amount); + } else { + // Need to convert currency of instant purchase maximum before trying to play + Lbryio.getExchangeRates().then(({ LBC_USD }) => { + attemptPlay(cost, instantPurchaseMax.amount / LBC_USD); + }); + } + }*/ + }; +} diff --git a/app/src/styles/fileDownloadButton.js b/app/src/styles/fileDownloadButton.js new file mode 100644 index 00000000..24ea0d81 --- /dev/null +++ b/app/src/styles/fileDownloadButton.js @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; + +const fileDownloadButtonStyle = StyleSheet.create({ + container: { + width: 120, + height: 36, + borderRadius: 18, + justifyContent: 'center', + backgroundColor: '#40c0a9', + }, + text: { + color: '#ffffff', + fontSize: 13, + textAlign: 'center' + } +}); + +export default fileDownloadButtonStyle; \ No newline at end of file diff --git a/app/src/styles/filePage.js b/app/src/styles/filePage.js index 8a6669fa..bc79baf5 100644 --- a/app/src/styles/filePage.js +++ b/app/src/styles/filePage.js @@ -12,7 +12,8 @@ const filePageStyle = StyleSheet.create({ flex: 1 }, mediaContainer: { - backgroundColor: '#000000' + backgroundColor: '#000000', + alignItems: 'center' }, emptyClaimText: { textAlign: 'center', @@ -49,6 +50,14 @@ const filePageStyle = StyleSheet.create({ thumbnail: { width: screenWidth, height: 200 + }, + downloadButton: { + position: 'absolute', + top: '50%' + }, + player: { + width: screenWidth, + height: 200 } }); diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/build.py b/p4a/pythonforandroid/bootstraps/lbry/build/build.py index f1e79c4b..0de26373 100755 --- a/p4a/pythonforandroid/bootstraps/lbry/build/build.py +++ b/p4a/pythonforandroid/bootstraps/lbry/build/build.py @@ -364,7 +364,6 @@ main.py that loads it.''') remove('AndroidManifest.xml') shutil.copy(join('src', 'main', 'AndroidManifest.xml'), 'AndroidManifest.xml') - render( 'strings.tmpl.xml', @@ -397,6 +396,18 @@ main.py that loads it.''') aars=aars, android_api=android_api, build_tools_version=build_tools_version) + + render( + 'settings.tmpl.gradle', + 'settings.gradle' + ) + + # copy icon drawables + for folder in ('drawable-hdpi', 'drawable-mdpi', 'drawable-xhdpi', 'drawable-xxhdpi', 'drawable-xxxhdpi'): + shutil.copy( + 'templates/res/{}/ic_file_download_black_24dp.png'.format(folder), + 'src/main/res/{}/ic_file_download_black_24dp.png'.format(folder) + ); ## ant build templates render( diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d9aacea4c67e78f5e6d75b133befa380f6d849c2 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8w5N+>NCo5DOB;Ea6-1Z=`wwaK zlr7#Ec`0zmrbkLnzs~>VT)6(!jYHeM3V+#XxRBFz+79ba7X-_-E8Go47Mzgi>REWg wMe|>k`#dLBo!|pTtSxP|xh8TPw&^vD-tvEwul@ZBw1t7e)78&qol`;+0AD0DVgLXD literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c845e8494bcc13406458c6ed3b1f9fd8618a1d GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1OHUWakP60R38@G6Onq4g7bP0l+XkK5V;_b literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f5afb24dc5724aefabc49520b22dfcb761e6dac2 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0Da8DP)a=3ghylE3c2%xjXsc0z{Fyzw{Joqi?vh|k>Fb%|&7*ZgcX-Cywh rKBv-wx%XKF1wV;r<(2c81cdTS$sUS|W!^RgXa$3(tDnm{r-UW|ZXqc?B^YPCZXI|4Q0T80z@JOXzdv<3r#1fAyL;U#1&qKZB>MpUXO@geCyfj6=8p literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8c83bffa7e4443c2f9bc5c037152366bbd2b2a8c GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeG3?%1&o4*=JaR&H=xB_ViXik240LWr43GxeO z_}|d-YF7`CTjJ^B7*fIb_UuMphX4_Wz@K-epY(rm$rSOv5x&UEQI+e--EU&)I|8Rx zn`YUVUt2QqqO7p8jPYX2Y1uALFv!Bh(&V7xf`I2(I$9v`=tPEz0uwjrB`PNJK1xr$ eb^X}yRc5jUbNLGR3)cYcXYh3Ob6Mw<&;$VajYK&B literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle index 34455ffc..2ed100cc 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle @@ -67,6 +67,7 @@ android { } dependencies { + compile project(':react-native-video') {%- for aar in aars %} compile(name: '{{ aar }}', ext: 'aar') {%- endfor -%} diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d9aacea4c67e78f5e6d75b133befa380f6d849c2 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8w5N+>NCo5DOB;Ea6-1Z=`wwaK zlr7#Ec`0zmrbkLnzs~>VT)6(!jYHeM3V+#XxRBFz+79ba7X-_-E8Go47Mzgi>REWg wMe|>k`#dLBo!|pTtSxP|xh8TPw&^vD-tvEwul@ZBw1t7e)78&qol`;+0AD0DVgLXD literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d50bdaae06ee5a8d3f39911f81715abd3bf7b24d GIT binary patch literal 2683 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 literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..c2c845e8494bcc13406458c6ed3b1f9fd8618a1d GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1OHUWakP60R38@G6Onq4g7bP0l+XkK5V;_b literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..0a299eb3cc0273ad1fc260cf0b4a2c35f5d373f5 GIT binary patch literal 1698 zcmV;T23`4yP)f} zu>|cMov+-ZzrP>60ufVbMfI5GD8S3sHU={Wz|3(0Iuu=S@d?FG>uvDs20IvRa=`Mf zj#y>tjJ0O2%(P!fEH>}&wob%R&H}5 zCGc-u(!|no5r`_`6U@PeT^`tAbpUc=l=XIx5}}*~W|%4>j>`bH?(t?i92~9nn5b`P z0>7Y$mEeQ`zFlE~MQf~ZG9n(urD7;YrS#B=Xsowzhmqy}5da!J%3kbpKFSQ6?LES3 zdZV=$HtqI=cTl8G16xp14uP;#cL2|TbNJ?WbCv}PLC1o@ra$3vG#sKQRjdsy89E-; znY%&Wrg-Hc0ikisFjZMa4gT2a!LsFbJVGacax#gWk55EjU*EU@vs5pnGl{G3Su9*> z!N$T5Ypq5G^s>zk;1`v_^H>BcvDMq12|&jy4-O2w$V^iek#aL6kcJj+tOIn78@KP` zS-n&hVAi+*!y%P5Bk6V~t9LpJEg6Dv^9^HW=&|J{j%XbP;NW?ZWriBBlQv?_b{7Wf z?jNR;`Laq0%pJT@Bot{6Kx_D6R>6O6CaG({;-O4fxdg!7FN{sE1|%b@0J(*wY`Ud# zI&>PHo!wYrvX5kIA6$-v>I9%5)46v*2=WER+5@z;cjB`jH^as)5b4Y=@KwIY|49kMQ$Jq^Dh2W~kwk@+x$8bu&m>dOMx`k@;AF zOr8K4IfY0k8eSOHMN@M#{DV%Rsz#yu)%WM&qwuw)N1vUEpCDpjN&f6V0!P?RG^g3&|W3X}!VA~OOk&(lPv85w2vT{aGqiO+W zYf57uSx8hDv*9QfJwZunB_z+Jkkm>cDyt-0fLi3{6{B7%LtVWLLei#2QU@6+!TiD! zl#~5aZJiXd#%46Pyg>bP8Oyk8^pf6|Hpt);7>u(~G3pkw+3Eo1=sLZnrDub4ArZ+b z_Yo2ni^%f{c*H9}V)8v)O}c|CM3)n8BIWKwa4ud)++{A##l(Y`E5KFmU4(Pu+2747 z*`g5=8ISd54mcALP0FuJ-Bx1G8vz)-chNQaRj#2MI5{az9zPF9gF_IX$VE?2kEYGs z!~iB@Qrq`{%xpdkcQ_(EDwdRD`FQUG69b?RqFdY^GAOBDH)`vilR+PWYe_e7@oFLp zi%XzX_GHnL8)uWgCx#|Gs=@G!ZNq|X!w*jD3DxnY32q2fsp%2msAeBm? z57GqikytE-K8Si%3m_B sCR#xB$vdJ2L!RajdHnFb`QMJe0XP&@60ho4VgLXD07*qoM6N<$f_SqK!T)a=3ghylE3c2%xjXsc0z{Fyzw{Joqi?vh|k>Fb%|&7*ZgcX-Cywh rKBv-wx%XKF1wV;r<(2c81cdTS$sUS|W!^RgXa$3(tDnm{r-UW|ZqaTn3XQ!tHPYHM zMO4&iY%%-veV@PJ`Ebs;u5&(|^Yz4;8tYtU;%5Q?;If{srukoW{y#9#{pID1*S7(H zg`}scZW%bcmF;ecvElDaT^OFJJjTv^$*;Ti;LFT#R`-WEepkZ;IVrQq7ItO*iUJ1n z;v!CUby^xpb51h`qadDQ-PB|$St=>r?<;Lb4MWPUi?XtJsq0AuzYBM|z>U2olP2<@ zbY(0U#rx>LB-a_6l%)ETOlA%3$Ky?iN2EJR#c{3NMpv?b^-vfY8B1X>n4E))iwl2S z>P?6Gf`DMp!mZxME!+2la*CeNvoPF3lf&aC9{Dh1@Ix;eH7=DwOo}Ny>LL$M;oty}0eDbZ3rlmO^PR@k-%G$Q?KH@6) z9|0;yPmkngLkG2L$N7NnZyRg?4FFOD{#Q{7JNb2unfcyoeyVVLsz`WuSDsG&6kOzM z(oKItI3iCc^y~mQ%fIT}MWRD4P!~LTcO~ zN?TPi3p;+XM*V8p)v%U_ghb!zTn0po4v++=>+FzbNFtDyx>rKvc|WUyw^e!K&>v{J zFVJx*yZmmkVV|aUu}$=!Tfv4r^$yc=Iibkpcdz%N)%_R3W0yyz&L}w?`ecAVd#-9> zp38#AqZ3zYC`;TJrUdSA56RE=g4vjnviLx#>cc95J)6+C5F7H6_Iq`0#aVy0A0G03 z)npczW4DqUF)qyq4A!z4(sd79CRkon5a66I4C!l5~s#Hn!OH;U$oxX z(m5UTx#^_${S6eoBaoGq>3%=U7U=Pq#6O`63%@{t0abe(twOp-k4KR@B%>)ubd#T| zy|W`-geXsqeN0qmaVd-I5v8G9ocok!53X2v5;=S-n4Dv_ZRk<5UH3d@f{pmMptof0 zhB!M-z8|}(6xoOwc+wX-hi>1J$FEua#%~SEi^Uda2cHlNPMxp}bf3#m{{k{98D&N&$q3t`7+q99srgYBV zPtIYYyqOuq$7-BNyLncIH(kH>(!KkaF#J)1C>rr5?4~53bSHuD-oUmv6~E6NJMzHs zKF=ql{{FwKfwj5i%)^QyvI^EvZUu3QU18Tp6$}np6v2dR`r zXy_jl2L>$%9OPA_Yp@0=kXq4UEtTYyg0|665VdS>PcS25g%%U#x;z#*CO^sUcSGpX z;E!6J|@D3c#F~W7GnIyu9+65Rx&X ziqH2FUaQM7Ay)RO0!Gs`?>{seSp}{q=%Gk1iEwN0$ITs}l~tb3`=Y#z1CqohUY&RJ z6Ekks*RY7;NAT+@u!4XqNVOMQl6=vz%_(1}-W302LaM20Q?m(jRVrcUza0-rGq$If zdutbpo8$#t6uN-spVbeZa6)e9Zma8R7WdLN`M`zMo?L$yk8!RO_Fe^U!Knh8aBA8c zS|;F(U2;6i?GtISz=|#nmjPE-9yHow2BPKUPn>lXh z0B3LiS|r%hG=h%KG-yM~`eh9wI#Izm|FXOwq9pB=;X`L+!u+zFBkutfh6fc>L@e#1 zoFrg)iYF|XOvFG1{NX|y^%BJL9_E}ZyZ$;b1g$Y^k$FY*ti0o6fyL0aabL@#2(L#y zo95C_RuP$w2MTFb)uTdIH3zdz3l#r=M`@zyBY)*xt17(~KE4AK;5H+)XP&db|^wJ&9U zx%0pXDI9#*01|h^x0})P2nP87oItCC&VILOQTA}}pEF=WE)I9<40|c0O|E8xu;9aQ z#&#^~_WUKZ)z7+oBL$;YxXkm=zR}|4-o9_X=6n7#(~A3qg9z!|y(*MH0wWde8C$@` zjWFax<#mt1pmLqJkEX~t#QR*1q-kZoT6Qs@Ew`E>JnMNeYCD~8@J>`4{+63?mBwXt zf}{c4J-p_JshFps+D;M!>Q9ki(7*Tzv4Xh zaW8pmgS7N+bDR51m#TpP4OV-oz<_;UJXSa@6)euMLYpPptP(5{vptey)of@uAj9ul zafunRFRLSunipGJtp5Wf6ozcGUAT}Y=8 zZqMzgm05wus~hy<0XK_4A1PCoub{b_MK!|g>{vz-hPwskLkruDMsftF(O2&pe6Q?q zh7P`68p`^7mtgqUn6D$^y2h}}2p=0@~ld?{6DBnwI z9&uWUZ_Fy^K(tj=_?$ZacK>9~tAtHqWPP z!y4`}{gNdZ`f$481iK|vQi9zxr^;ZC6jtC)pIE$zjEObS z?Ub|OzLGm{SMR4~`jqDMoI3ZdB(Nqsg6iE>gcKe8=-R305uCqgTebKGS-4CJvkILD zncC?GWzro$-*eS1u5QH~_bPjTn~jSWez-~-7uepsq}3Z02uuuGHd#1K_1(X-7VNLG zhAIuGDfgNZYs2!A>;iXol2kSh;=jrV1IW78b$VHH+=%rzW(!F1g9TZiLi z$Vus?wmmZn(NfIA{mI%)0UL#uMojc}f|Yf{n&5k>pl5!7`qSqPZ(jXWVTtbKg!iRh zL!Fvr^{y&8G^UO4OGCKH!9&`Z(ndE zInYzqS{md4uZ)E2K*?c}A&N>h&zK$0#m;_kw^-@mF_N1B{{DrnRqRi=-g&F=A?yrl zMei!H$W_@iEOVqLKQxUBvNk?EI1rd8UJd2aRCqgSG0TVUEpZ!{{EbZ^%a@nGI6i7r zJ}@1J%0?=-cM=3L8=}v8rpwp)EiJA8Eygh){tmuSxprV35uhjli2Wao6L_~mEKWP8EA)Gxio?JWcsReW8pf5C)m6f_plSIH5dw%#L z!CG{Ft&1%b0f+(&02Km|o!lvx%_M-Dr2s=2ME)W5`B}pm`5$-zTP^?q literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ce97c85dfa3544a0246aa9604effbfd8a9b8d75f GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawN<3X0Ln;{Gp4-UCWXRBZq37JQ z<#+XSgBE;>Xqc?B^YPCZXI|4Q0T80z@JOXzdv<3r#1fAyL;U#1&qKZB>MpUXO@geCyfj6=8p literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d423dac2624cf0b5dc90821a15362bc29e5a1e6b GIT binary patch literal 6874 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 literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8c83bffa7e4443c2f9bc5c037152366bbd2b2a8c GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeG3?%1&o4*=JaR&H=xB_ViXik240LWr43GxeO z_}|d-YF7`CTjJ^B7*fIb_UuMphX4_Wz@K-epY(rm$rSOv5x&UEQI+e--EU&)I|8Rx zn`YUVUt2QqqO7p8jPYX2Y1uALFv!Bh(&V7xf`I2(I$9v`=tPEz0uwjrB`PNJK1xr$ eb^X}yRc5jUbNLGR3)cYcXYh3Ob6Mw<&;$VajYK&B literal 0 HcmV?d00001 diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle new file mode 100644 index 00000000..d4b80367 --- /dev/null +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'lbrynet' +include ':react-native-video' +project(':react-native-video').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-video/android') diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..48e341a0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/src/main/assets/index.android.bundle b/src/main/assets/index.android.bundle index d8e53344..9a0aa646 100644 --- a/src/main/assets/index.android.bundle +++ b/src/main/assets/index.android.bundle @@ -1556,7 +1556,7 @@ __d(function (global, require, module, exports, _dependencyMap) { }); exports.default = LBRYApp; -},11,[12,22,61,66,36,376,609,621,633,635,642,62,616],"LBRYApp/src/index.js"); +},11,[12,22,61,66,36,376,609,628,640,642,649,62,623],"LBRYApp/src/index.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -6866,6 +6866,15 @@ __d(function (global, require, module, exports, _dependencyMap) { }); }; + Lbry.get = function () { + var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return new Promise(function (resolve, reject) { + apiCall('get', params, function (streamInfo) { + resolve(streamInfo); + }, reject); + }); + }; + Lbry.resolve = function () { var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return new Promise(function (resolve, reject) { @@ -74639,7 +74648,7 @@ __d(function (global, require, module, exports, _dependencyMap) { }; exports.default = (0, _reactRedux.connect)(mapStateToProps)(AppWithNavigationState); -},609,[12,61,610,613,376,22,616,66,455,454],"LBRYApp/src/component/AppNavigator.js"); +},609,[12,61,610,620,376,22,623,66,455,454],"LBRYApp/src/component/AppNavigator.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -74690,22 +74699,49 @@ __d(function (global, require, module, exports, _dependencyMap) { var _react2 = babelHelpers.interopRequireDefault(_react); - var _reactNative = require(_dependencyMap[1], "react-native"); + var _lbryRedux = require(_dependencyMap[1], "lbry-redux"); - var _filePage = require(_dependencyMap[2], "../../styles/filePage"); + var _reactNative = require(_dependencyMap[2], "react-native"); + + var _reactNativeVideo = require(_dependencyMap[3], "react-native-video"); + + var _reactNativeVideo2 = babelHelpers.interopRequireDefault(_reactNativeVideo); + + var _filePage = require(_dependencyMap[4], "../../styles/filePage"); var _filePage2 = babelHelpers.interopRequireDefault(_filePage); - var _fileItemMedia = require(_dependencyMap[3], "../../component/fileItemMedia"); + var _fileItemMedia = require(_dependencyMap[5], "../../component/fileItemMedia"); var _fileItemMedia2 = babelHelpers.interopRequireDefault(_fileItemMedia); + var _fileDownloadButton = require(_dependencyMap[6], "../../component/fileDownloadButton"); + + var _fileDownloadButton2 = babelHelpers.interopRequireDefault(_fileDownloadButton); + var FilePage = function (_React$PureComponent) { babelHelpers.inherits(FilePage, _React$PureComponent); function FilePage() { + var _ref; + + var _temp, _this, _ret; + babelHelpers.classCallCheck(this, FilePage); - return babelHelpers.possibleConstructorReturn(this, (FilePage.__proto__ || Object.getPrototypeOf(FilePage)).apply(this, arguments)); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _ret = (_temp = (_this = babelHelpers.possibleConstructorReturn(this, (_ref = FilePage.__proto__ || Object.getPrototypeOf(FilePage)).call.apply(_ref, [this].concat(args))), _this), _this.state = { + rate: 1, + volume: 1, + muted: false, + resizeMode: 'contain', + duration: 0.0, + currentTime: 0.0, + paused: true + }, _temp), babelHelpers.possibleConstructorReturn(_this, _ret); } babelHelpers.createClass(FilePage, [{ @@ -74736,14 +74772,16 @@ __d(function (global, require, module, exports, _dependencyMap) { }, { key: "render", value: function render() { + var _this2 = this; + var _props = this.props, claim = _props.claim, fileInfo = _props.fileInfo, metadata = _props.metadata, contentType = _props.contentType, tab = _props.tab, - uri = _props.uri, - rewardedContentClaimIds = _props.rewardedContentClaimIds; + rewardedContentClaimIds = _props.rewardedContentClaimIds, + navigation = _props.navigation; if (!claim || !metadata) { return _react2.default.createElement( @@ -74752,7 +74790,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.container, __source: { fileName: _jsxFileName, - lineNumber: 45 + lineNumber: 58 } }, _react2.default.createElement( @@ -74761,7 +74799,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.emptyClaimText, __source: { fileName: _jsxFileName, - lineNumber: 46 + lineNumber: 59 } }, "Empty claim or metadata info." @@ -74769,9 +74807,14 @@ __d(function (global, require, module, exports, _dependencyMap) { ); } + var completed = fileInfo && fileInfo.completed; var title = metadata.title; var isRewardContent = rewardedContentClaimIds.includes(claim.claim_id); var description = metadata.description ? metadata.description : null; + + var mediaType = _lbryRedux.Lbry.getMediaType(contentType); + + var isPlayable = mediaType === 'video' || mediaType === 'audio'; var height = claim.height, channelName = claim.channel_name, value = claim.value; @@ -74782,7 +74825,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.pageContainer, __source: { fileName: _jsxFileName, - lineNumber: 65 + lineNumber: 75 } }, _react2.default.createElement( @@ -74791,18 +74834,56 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.mediaContainer, __source: { fileName: _jsxFileName, - lineNumber: 66 + lineNumber: 76 } }, - _react2.default.createElement(_fileItemMedia2.default, { + (!fileInfo || !isPlayable) && _react2.default.createElement(_fileItemMedia2.default, { style: _filePage2.default.thumbnail, title: title, thumbnail: metadata.thumbnail, __source: { fileName: _jsxFileName, - lineNumber: 67 + lineNumber: 77 } - }) + }), + !completed && _react2.default.createElement(_fileDownloadButton2.default, { + uri: navigation.state.params.uri, + style: _filePage2.default.downloadButton, + __source: { + fileName: _jsxFileName, + lineNumber: 78 + } + }), + fileInfo && isPlayable && _react2.default.createElement( + _reactNative.TouchableOpacity, + { + style: _filePage2.default.player, + onPress: function onPress() { + return _this2.setState({ + paused: !_this2.state.paused + }); + }, + __source: { + fileName: _jsxFileName, + lineNumber: 81 + } + }, + _react2.default.createElement(_reactNativeVideo2.default, { + source: { + uri: 'file:///' + fileInfo.download_path + }, + resizeMode: "cover", + playInBackground: true, + style: _filePage2.default.player, + rate: this.state.rate, + volume: this.state.volume, + paused: this.state.paused, + __source: { + fileName: _jsxFileName, + lineNumber: 84 + } + }) + ) ), _react2.default.createElement( _reactNative.ScrollView, @@ -74810,7 +74891,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.scrollContainer, __source: { fileName: _jsxFileName, - lineNumber: 69 + lineNumber: 96 } }, _react2.default.createElement( @@ -74819,7 +74900,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.title, __source: { fileName: _jsxFileName, - lineNumber: 70 + lineNumber: 97 } }, title @@ -74830,7 +74911,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.channelName, __source: { fileName: _jsxFileName, - lineNumber: 71 + lineNumber: 98 } }, channelName @@ -74841,7 +74922,7 @@ __d(function (global, require, module, exports, _dependencyMap) { style: _filePage2.default.description, __source: { fileName: _jsxFileName, - lineNumber: 72 + lineNumber: 99 } }, description @@ -74857,7 +74938,411 @@ __d(function (global, require, module, exports, _dependencyMap) { title: '' }; exports.default = FilePage; -},611,[12,66,612,449],"LBRYApp/src/page/file/view.js"); +},611,[12,62,66,612,615,449,616],"LBRYApp/src/page/file/view.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _jsxFileName = "/home/akinwale/Dev/Python/lbry-android/app/node_modules/react-native-video/Video.js"; + + var _react = require(_dependencyMap[0], "react"); + + var _react2 = babelHelpers.interopRequireDefault(_react); + + var _propTypes = require(_dependencyMap[1], "prop-types"); + + var _propTypes2 = babelHelpers.interopRequireDefault(_propTypes); + + var _reactNative = require(_dependencyMap[2], "react-native"); + + var _resolveAssetSource = require(_dependencyMap[3], "react-native/Libraries/Image/resolveAssetSource"); + + var _resolveAssetSource2 = babelHelpers.interopRequireDefault(_resolveAssetSource); + + var _VideoResizeMode = require(_dependencyMap[4], "./VideoResizeMode.js"); + + var _VideoResizeMode2 = babelHelpers.interopRequireDefault(_VideoResizeMode); + + var styles = _reactNative.StyleSheet.create({ + base: { + overflow: 'hidden' + } + }); + + var Video = function (_Component) { + babelHelpers.inherits(Video, _Component); + + function Video(props) { + babelHelpers.classCallCheck(this, Video); + + var _this = babelHelpers.possibleConstructorReturn(this, (Video.__proto__ || Object.getPrototypeOf(Video)).call(this, props)); + + _this.seek = function (time) { + _this.setNativeProps({ + seek: time + }); + }; + + _this.presentFullscreenPlayer = function () { + _this.setNativeProps({ + fullscreen: true + }); + }; + + _this.dismissFullscreenPlayer = function () { + _this.setNativeProps({ + fullscreen: false + }); + }; + + _this._assignRoot = function (component) { + _this._root = component; + }; + + _this._onLoadStart = function (event) { + if (_this.props.onLoadStart) { + _this.props.onLoadStart(event.nativeEvent); + } + }; + + _this._onLoad = function (event) { + if (_this.props.onLoad) { + _this.props.onLoad(event.nativeEvent); + } + }; + + _this._onError = function (event) { + if (_this.props.onError) { + _this.props.onError(event.nativeEvent); + } + }; + + _this._onProgress = function (event) { + if (_this.props.onProgress) { + _this.props.onProgress(event.nativeEvent); + } + }; + + _this._onSeek = function (event) { + if (_this.state.showPoster) { + _this.setState({ + showPoster: false + }); + } + + if (_this.props.onSeek) { + _this.props.onSeek(event.nativeEvent); + } + }; + + _this._onEnd = function (event) { + if (_this.props.onEnd) { + _this.props.onEnd(event.nativeEvent); + } + }; + + _this._onTimedMetadata = function (event) { + if (_this.props.onTimedMetadata) { + _this.props.onTimedMetadata(event.nativeEvent); + } + }; + + _this._onFullscreenPlayerWillPresent = function (event) { + if (_this.props.onFullscreenPlayerWillPresent) { + _this.props.onFullscreenPlayerWillPresent(event.nativeEvent); + } + }; + + _this._onFullscreenPlayerDidPresent = function (event) { + if (_this.props.onFullscreenPlayerDidPresent) { + _this.props.onFullscreenPlayerDidPresent(event.nativeEvent); + } + }; + + _this._onFullscreenPlayerWillDismiss = function (event) { + if (_this.props.onFullscreenPlayerWillDismiss) { + _this.props.onFullscreenPlayerWillDismiss(event.nativeEvent); + } + }; + + _this._onFullscreenPlayerDidDismiss = function (event) { + if (_this.props.onFullscreenPlayerDidDismiss) { + _this.props.onFullscreenPlayerDidDismiss(event.nativeEvent); + } + }; + + _this._onReadyForDisplay = function (event) { + if (_this.props.onReadyForDisplay) { + _this.props.onReadyForDisplay(event.nativeEvent); + } + }; + + _this._onPlaybackStalled = function (event) { + if (_this.props.onPlaybackStalled) { + _this.props.onPlaybackStalled(event.nativeEvent); + } + }; + + _this._onPlaybackResume = function (event) { + if (_this.props.onPlaybackResume) { + _this.props.onPlaybackResume(event.nativeEvent); + } + }; + + _this._onPlaybackRateChange = function (event) { + if (_this.state.showPoster && event.nativeEvent.playbackRate !== 0) { + _this.setState({ + showPoster: false + }); + } + + if (_this.props.onPlaybackRateChange) { + _this.props.onPlaybackRateChange(event.nativeEvent); + } + }; + + _this._onAudioBecomingNoisy = function () { + if (_this.props.onAudioBecomingNoisy) { + _this.props.onAudioBecomingNoisy(); + } + }; + + _this._onAudioFocusChanged = function (event) { + if (_this.props.onAudioFocusChanged) { + _this.props.onAudioFocusChanged(event.nativeEvent); + } + }; + + _this._onBuffer = function (event) { + if (_this.props.onBuffer) { + _this.props.onBuffer(event.nativeEvent); + } + }; + + _this.state = { + showPoster: true + }; + return _this; + } + + babelHelpers.createClass(Video, [{ + key: "setNativeProps", + value: function setNativeProps(nativeProps) { + this._root.setNativeProps(nativeProps); + } + }, { + key: "render", + value: function render() { + var resizeMode = this.props.resizeMode; + var source = (0, _resolveAssetSource2.default)(this.props.source) || {}; + var uri = source.uri || ''; + + if (uri && uri.match(/^\//)) { + uri = "file://" + uri; + } + + var isNetwork = !!(uri && uri.match(/^https?:/)); + var isAsset = !!(uri && uri.match(/^(assets-library|file|content|ms-appx|ms-appdata):/)); + var nativeResizeMode = void 0; + + if (resizeMode === _VideoResizeMode2.default.stretch) { + nativeResizeMode = _reactNative.NativeModules.UIManager.RCTVideo.Constants.ScaleToFill; + } else if (resizeMode === _VideoResizeMode2.default.contain) { + nativeResizeMode = _reactNative.NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit; + } else if (resizeMode === _VideoResizeMode2.default.cover) { + nativeResizeMode = _reactNative.NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill; + } else { + nativeResizeMode = _reactNative.NativeModules.UIManager.RCTVideo.Constants.ScaleNone; + } + + var nativeProps = babelHelpers.extends({}, this.props); + babelHelpers.extends(nativeProps, { + style: [styles.base, nativeProps.style], + resizeMode: nativeResizeMode, + src: { + uri: uri, + isNetwork: isNetwork, + isAsset: isAsset, + type: source.type || '', + mainVer: source.mainVer || 0, + patchVer: source.patchVer || 0 + }, + onVideoLoadStart: this._onLoadStart, + onVideoLoad: this._onLoad, + onVideoError: this._onError, + onVideoProgress: this._onProgress, + onVideoSeek: this._onSeek, + onVideoEnd: this._onEnd, + onVideoBuffer: this._onBuffer, + onTimedMetadata: this._onTimedMetadata, + onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent, + onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent, + onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss, + onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss, + onReadyForDisplay: this._onReadyForDisplay, + onPlaybackStalled: this._onPlaybackStalled, + onPlaybackResume: this._onPlaybackResume, + onPlaybackRateChange: this._onPlaybackRateChange, + onAudioFocusChanged: this._onAudioFocusChanged, + onAudioBecomingNoisy: this._onAudioBecomingNoisy + }); + + if (this.props.poster && this.state.showPoster) { + var posterStyle = { + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + resizeMode: 'contain' + }; + return _react2.default.createElement( + _reactNative.View, + { + style: nativeProps.style, + __source: { + fileName: _jsxFileName, + lineNumber: 225 + } + }, + _react2.default.createElement(RCTVideo, babelHelpers.extends({ + ref: this._assignRoot + }, nativeProps, { + __source: { + fileName: _jsxFileName, + lineNumber: 226 + } + })), + _react2.default.createElement(_reactNative.Image, { + style: posterStyle, + source: { + uri: this.props.poster + }, + __source: { + fileName: _jsxFileName, + lineNumber: 230 + } + }) + ); + } + + return _react2.default.createElement(RCTVideo, babelHelpers.extends({ + ref: this._assignRoot + }, nativeProps, { + __source: { + fileName: _jsxFileName, + lineNumber: 239 + } + })); + } + }]); + return Video; + }(_react.Component); + + exports.default = Video; + Video.propTypes = babelHelpers.extends({ + src: _propTypes2.default.object, + seek: _propTypes2.default.number, + fullscreen: _propTypes2.default.bool, + onVideoLoadStart: _propTypes2.default.func, + onVideoLoad: _propTypes2.default.func, + onVideoBuffer: _propTypes2.default.func, + onVideoError: _propTypes2.default.func, + onVideoProgress: _propTypes2.default.func, + onVideoSeek: _propTypes2.default.func, + onVideoEnd: _propTypes2.default.func, + onTimedMetadata: _propTypes2.default.func, + onVideoFullscreenPlayerWillPresent: _propTypes2.default.func, + onVideoFullscreenPlayerDidPresent: _propTypes2.default.func, + onVideoFullscreenPlayerWillDismiss: _propTypes2.default.func, + onVideoFullscreenPlayerDidDismiss: _propTypes2.default.func, + source: _propTypes2.default.oneOfType([_propTypes2.default.shape({ + uri: _propTypes2.default.string + }), _propTypes2.default.number]), + resizeMode: _propTypes2.default.string, + poster: _propTypes2.default.string, + repeat: _propTypes2.default.bool, + paused: _propTypes2.default.bool, + muted: _propTypes2.default.bool, + volume: _propTypes2.default.number, + rate: _propTypes2.default.number, + playInBackground: _propTypes2.default.bool, + playWhenInactive: _propTypes2.default.bool, + ignoreSilentSwitch: _propTypes2.default.oneOf(['ignore', 'obey']), + disableFocus: _propTypes2.default.bool, + controls: _propTypes2.default.bool, + currentTime: _propTypes2.default.number, + progressUpdateInterval: _propTypes2.default.number, + onLoadStart: _propTypes2.default.func, + onLoad: _propTypes2.default.func, + onBuffer: _propTypes2.default.func, + onError: _propTypes2.default.func, + onProgress: _propTypes2.default.func, + onSeek: _propTypes2.default.func, + onEnd: _propTypes2.default.func, + onFullscreenPlayerWillPresent: _propTypes2.default.func, + onFullscreenPlayerDidPresent: _propTypes2.default.func, + onFullscreenPlayerWillDismiss: _propTypes2.default.func, + onFullscreenPlayerDidDismiss: _propTypes2.default.func, + onReadyForDisplay: _propTypes2.default.func, + onPlaybackStalled: _propTypes2.default.func, + onPlaybackResume: _propTypes2.default.func, + onPlaybackRateChange: _propTypes2.default.func, + onAudioFocusChanged: _propTypes2.default.func, + onAudioBecomingNoisy: _propTypes2.default.func, + scaleX: _propTypes2.default.number, + scaleY: _propTypes2.default.number, + translateX: _propTypes2.default.number, + translateY: _propTypes2.default.number, + rotation: _propTypes2.default.number + }, _reactNative.View.propTypes); + var RCTVideo = (0, _reactNative.requireNativeComponent)('RCTVideo', Video, { + nativeOnly: { + src: true, + seek: true, + fullscreen: true + } + }); +},612,[12,24,66,201,613],"react-native-video/Video.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _keymirror = require(_dependencyMap[0], "keymirror"); + + var _keymirror2 = babelHelpers.interopRequireDefault(_keymirror); + + exports.default = (0, _keymirror2.default)({ + contain: null, + cover: null, + stretch: null + }); +},613,[614],"react-native-video/VideoResizeMode.js"); +__d(function (global, require, module, exports, _dependencyMap) { + "use strict"; + + var keyMirror = function keyMirror(obj) { + var ret = {}; + var key; + + if (!(obj instanceof Object && !Array.isArray(obj))) { + throw new Error('keyMirror(...): Argument must be an object.'); + } + + for (key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + + ret[key] = key; + } + + return ret; + }; + + module.exports = keyMirror; +},614,[],"keymirror/index.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -74878,7 +75363,8 @@ __d(function (global, require, module, exports, _dependencyMap) { flex: 1 }, mediaContainer: { - backgroundColor: '#000000' + backgroundColor: '#000000', + alignItems: 'center' }, emptyClaimText: { textAlign: 'center', @@ -74915,11 +75401,458 @@ __d(function (global, require, module, exports, _dependencyMap) { thumbnail: { width: screenWidth, height: 200 + }, + downloadButton: { + position: 'absolute', + top: '50%' + }, + player: { + width: screenWidth, + height: 200 } }); exports.default = filePageStyle; -},612,[66],"LBRYApp/src/styles/filePage.js"); +},615,[66],"LBRYApp/src/styles/filePage.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _reactRedux = require(_dependencyMap[0], "react-redux"); + + var _lbryRedux = require(_dependencyMap[1], "lbry-redux"); + + var _file = require(_dependencyMap[2], "../../redux/actions/file"); + + var _view = require(_dependencyMap[3], "./view"); + + var _view2 = babelHelpers.interopRequireDefault(_view); + + var select = function select(state, props) { + return { + fileInfo: (0, _lbryRedux.makeSelectFileInfoForUri)(props.uri)(state), + downloading: (0, _lbryRedux.makeSelectDownloadingForUri)(props.uri)(state), + costInfo: (0, _lbryRedux.makeSelectCostInfoForUri)(props.uri)(state), + loading: (0, _lbryRedux.makeSelectLoadingForUri)(props.uri)(state) + }; + }; + + var perform = function perform(dispatch) { + return { + purchaseUri: function purchaseUri(uri) { + return dispatch((0, _file.doPurchaseUri)(uri)); + }, + restartDownload: function restartDownload(uri, outpoint) { + return dispatch((0, _file.doStartDownload)(uri, outpoint)); + } + }; + }; + + exports.default = (0, _reactRedux.connect)(select, perform)(_view2.default); +},616,[22,62,617,618],"LBRYApp/src/component/fileDownloadButton/index.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.doUpdateLoadStatus = doUpdateLoadStatus; + exports.doStartDownload = doStartDownload; + exports.doDownloadFile = doDownloadFile; + exports.doSetPlayingUri = doSetPlayingUri; + exports.doLoadVideo = doLoadVideo; + exports.doPurchaseUri = doPurchaseUri; + + var _lbryRedux = require(_dependencyMap[0], "lbry-redux"); + + var _reactNative = require(_dependencyMap[1], "react-native"); + + var DOWNLOAD_POLL_INTERVAL = 250; + + function doUpdateLoadStatus(uri, outpoint) { + return function (dispatch, getState) { + _lbryRedux.Lbry.file_list({ + outpoint: outpoint, + full_status: true + }).then(function (_ref) { + var _ref2 = babelHelpers.slicedToArray(_ref, 1), + fileInfo = _ref2[0]; + + if (!fileInfo || fileInfo.written_bytes === 0) { + setTimeout(function () { + dispatch(doUpdateLoadStatus(uri, outpoint)); + }, DOWNLOAD_POLL_INTERVAL); + } else if (fileInfo.completed) { + var totalBytes = fileInfo.total_bytes, + writtenBytes = fileInfo.written_bytes; + dispatch({ + type: _lbryRedux.ACTIONS.DOWNLOADING_COMPLETED, + data: { + uri: uri, + outpoint: outpoint, + fileInfo: fileInfo + } + }); + + _reactNative.NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, 100, writtenBytes, totalBytes); + } else { + var _totalBytes = fileInfo.total_bytes, + _writtenBytes = fileInfo.written_bytes; + var progress = _writtenBytes / _totalBytes * 100; + dispatch({ + type: _lbryRedux.ACTIONS.DOWNLOADING_PROGRESSED, + data: { + uri: uri, + outpoint: outpoint, + fileInfo: fileInfo, + progress: progress + } + }); + + _reactNative.NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, progress, _writtenBytes, _totalBytes); + + setTimeout(function () { + dispatch(doUpdateLoadStatus(uri, outpoint)); + }, DOWNLOAD_POLL_INTERVAL); + } + }); + }; + } + + function doStartDownload(uri, outpoint) { + return function (dispatch, getState) { + var state = getState(); + + if (!outpoint) { + throw new Error('outpoint is required to begin a download'); + } + + var _state$fileInfo$downl = state.fileInfo.downloadingByOutpoint, + downloadingByOutpoint = _state$fileInfo$downl === undefined ? {} : _state$fileInfo$downl; + if (downloadingByOutpoint[outpoint]) return; + + _lbryRedux.Lbry.file_list({ + outpoint: outpoint, + full_status: true + }).then(function (_ref3) { + var _ref4 = babelHelpers.slicedToArray(_ref3, 1), + fileInfo = _ref4[0]; + + dispatch({ + type: _lbryRedux.ACTIONS.DOWNLOADING_STARTED, + data: { + uri: uri, + outpoint: outpoint, + fileInfo: fileInfo + } + }); + + _reactNative.NativeModules.LbryDownloadManager.startDownload(uri, fileInfo.file_name); + + dispatch(doUpdateLoadStatus(uri, outpoint)); + }); + }; + } + + function doDownloadFile(uri, streamInfo) { + return function (dispatch) { + dispatch(doStartDownload(uri, streamInfo.outpoint)); + }; + } + + function doSetPlayingUri(uri) { + return function (dispatch) { + dispatch({ + type: _lbryRedux.ACTIONS.SET_PLAYING_URI, + data: { + uri: uri + } + }); + }; + } + + function doLoadVideo(uri) { + return function (dispatch) { + dispatch({ + type: _lbryRedux.ACTIONS.LOADING_VIDEO_STARTED, + data: { + uri: uri + } + }); + + _lbryRedux.Lbry.get({ + uri: uri + }).then(function (streamInfo) { + var timeout = streamInfo === null || typeof streamInfo !== 'object' || streamInfo.error === 'Timeout'; + + if (timeout) { + dispatch(doSetPlayingUri(null)); + dispatch({ + type: _lbryRedux.ACTIONS.LOADING_VIDEO_FAILED, + data: { + uri: uri + } + }); + console.log("File timeout for uri " + uri); + } else { + dispatch(doDownloadFile(uri, streamInfo)); + } + }).catch(function () { + dispatch(doSetPlayingUri(null)); + dispatch({ + type: _lbryRedux.ACTIONS.LOADING_VIDEO_FAILED, + data: { + uri: uri + } + }); + console.log("Failed to download " + uri); + }); + }; + } + + function doPurchaseUri(uri, specificCostInfo) { + return function (dispatch, getState) { + var state = getState(); + var balance = 0; + var fileInfo = (0, _lbryRedux.makeSelectFileInfoForUri)(uri)(state); + var downloadingByOutpoint = (0, _lbryRedux.selectDownloadingByOutpoint)(state); + var alreadyDownloading = fileInfo && !!downloadingByOutpoint[fileInfo.outpoint]; + + function attemptPlay(cost) { + var instantPurchaseMax = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + + if (cost > 0 && (!instantPurchaseMax || cost > instantPurchaseMax)) { + console.log('Affirm purchase...'); + } else { + dispatch(doLoadVideo(uri)); + } + } + + if (fileInfo && fileInfo.completed) { + if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri)); + Promise.resolve(); + return; + } + + if (alreadyDownloading) { + Promise.resolve(); + return; + } + + var costInfo = (0, _lbryRedux.makeSelectCostInfoForUri)(uri)(state) || specificCostInfo; + var cost = costInfo.cost; + + if (cost > balance) { + dispatch(doSetPlayingUri(null)); + Promise.resolve(); + return; + } + + if (cost === 0) { + attemptPlay(cost); + } + }; + } +},617,[62,66],"LBRYApp/src/redux/actions/file.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + var _jsxFileName = "/home/akinwale/Dev/Python/lbry-android/app/src/component/fileDownloadButton/view.js"; + + var _react = require(_dependencyMap[0], "react"); + + var _react2 = babelHelpers.interopRequireDefault(_react); + + var _reactNative = require(_dependencyMap[1], "react-native"); + + var _fileDownloadButton = require(_dependencyMap[2], "../../styles/fileDownloadButton"); + + var _fileDownloadButton2 = babelHelpers.interopRequireDefault(_fileDownloadButton); + + var FileDownloadButton = function (_React$PureComponent) { + babelHelpers.inherits(FileDownloadButton, _React$PureComponent); + + function FileDownloadButton() { + babelHelpers.classCallCheck(this, FileDownloadButton); + return babelHelpers.possibleConstructorReturn(this, (FileDownloadButton.__proto__ || Object.getPrototypeOf(FileDownloadButton)).apply(this, arguments)); + } + + babelHelpers.createClass(FileDownloadButton, [{ + key: "componentWillReceiveProps", + value: function componentWillReceiveProps(nextProps) { + this.restartDownload(nextProps); + } + }, { + key: "restartDownload", + value: function restartDownload(props) { + var downloading = props.downloading, + fileInfo = props.fileInfo, + uri = props.uri, + restartDownload = props.restartDownload; + + if (!downloading && fileInfo && !fileInfo.completed && fileInfo.written_bytes !== false && fileInfo.written_bytes < fileInfo.total_bytes) { + restartDownload(uri, fileInfo.outpoint); + } + } + }, { + key: "render", + value: function render() { + var _props = this.props, + fileInfo = _props.fileInfo, + downloading = _props.downloading, + uri = _props.uri, + purchaseUri = _props.purchaseUri, + costInfo = _props.costInfo, + loading = _props.loading, + doPause = _props.doPause, + style = _props.style; + + var openFile = function openFile() {}; + + if (loading || downloading) { + var progress = fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, + label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...'; + return _react2.default.createElement( + _reactNative.View, + { + style: [style, _fileDownloadButton2.default.container], + __source: { + fileName: _jsxFileName, + lineNumber: 48 + } + }, + _react2.default.createElement(_reactNative.View, { + style: { + width: progress + "%", + backgroundColor: '#ff0000', + position: 'absolute', + left: 0, + top: 0 + }, + __source: { + fileName: _jsxFileName, + lineNumber: 49 + } + }), + _react2.default.createElement( + _reactNative.Text, + { + style: _fileDownloadButton2.default.text, + __source: { + fileName: _jsxFileName, + lineNumber: 50 + } + }, + label + ) + ); + } else if (fileInfo === null && !downloading) { + if (!costInfo) { + return _react2.default.createElement( + _reactNative.View, + { + style: [style, _fileDownloadButton2.default.container], + __source: { + fileName: _jsxFileName, + lineNumber: 56 + } + }, + _react2.default.createElement( + _reactNative.Text, + { + __source: { + fileName: _jsxFileName, + lineNumber: 57 + } + }, + "Fetching cost info..." + ) + ); + } + + return _react2.default.createElement( + _reactNative.TouchableOpacity, + { + style: [style, _fileDownloadButton2.default.container], + onPress: function onPress() { + purchaseUri(uri); + }, + __source: { + fileName: _jsxFileName, + lineNumber: 62 + } + }, + _react2.default.createElement( + _reactNative.Text, + { + style: _fileDownloadButton2.default.text, + __source: { + fileName: _jsxFileName, + lineNumber: 65 + } + }, + "Download" + ) + ); + } else if (fileInfo && fileInfo.download_path) { + return _react2.default.createElement( + _reactNative.TouchableOpacity, + { + style: [style, _fileDownloadButton2.default.container], + onPress: function onPress() { + return openFile(); + }, + __source: { + fileName: _jsxFileName, + lineNumber: 70 + } + }, + _react2.default.createElement( + _reactNative.Text, + { + style: _fileDownloadButton2.default.text, + __source: { + fileName: _jsxFileName, + lineNumber: 71 + } + }, + "Open" + ) + ); + } + + return null; + } + }]); + return FileDownloadButton; + }(_react2.default.PureComponent); + + exports.default = FileDownloadButton; +},618,[12,66,619],"LBRYApp/src/component/fileDownloadButton/view.js"); +__d(function (global, require, module, exports, _dependencyMap) { + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _reactNative = require(_dependencyMap[0], "react-native"); + + var fileDownloadButtonStyle = _reactNative.StyleSheet.create({ + container: { + width: 120, + height: 36, + borderRadius: 18, + justifyContent: 'center', + backgroundColor: '#40c0a9' + }, + text: { + color: '#ffffff', + fontSize: 13, + textAlign: 'center' + } + }); + + exports.default = fileDownloadButtonStyle; +},619,[66],"LBRYApp/src/styles/fileDownloadButton.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -74940,7 +75873,7 @@ __d(function (global, require, module, exports, _dependencyMap) { }; exports.default = (0, _reactRedux.connect)(select, perform)(_view2.default); -},613,[22,614],"LBRYApp/src/page/splash/index.js"); +},620,[22,621],"LBRYApp/src/page/splash/index.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75110,7 +76043,7 @@ __d(function (global, require, module, exports, _dependencyMap) { title: 'Splash' }; exports.default = SplashScreen; -},614,[12,62,66,24,615],"LBRYApp/src/page/splash/view.js"); +},621,[12,62,66,24,622],"LBRYApp/src/page/splash/view.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75150,7 +76083,7 @@ __d(function (global, require, module, exports, _dependencyMap) { }); exports.default = splashStyle; -},615,[66],"LBRYApp/src/styles/splash.js"); +},622,[66],"LBRYApp/src/styles/splash.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75165,7 +76098,7 @@ __d(function (global, require, module, exports, _dependencyMap) { var addListener = (0, _reactNavigationReduxHelpers.createReduxBoundAddListener)("root"); exports.reactNavigationMiddleware = reactNavigationMiddleware; exports.addListener = addListener; -},616,[617],"LBRYApp/src/utils/redux.js"); +},623,[624],"LBRYApp/src/utils/redux.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75199,8 +76132,8 @@ __d(function (global, require, module, exports, _dependencyMap) { var _reducer = require(_dependencyMap[2], "./reducer"); exports.createNavigationReducer = _reducer.createNavigationReducer; -},617,[618,619,620],"react-navigation-redux-helpers/src/index.js"); -__d(function (global, require, module, exports, _dependencyMap) {},618,[],"react-navigation-redux-helpers/src/types.js"); +},624,[625,626,627],"react-navigation-redux-helpers/src/index.js"); +__d(function (global, require, module, exports, _dependencyMap) {},625,[],"react-navigation-redux-helpers/src/types.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75277,7 +76210,7 @@ __d(function (global, require, module, exports, _dependencyMap) { exports.createReactNavigationReduxMiddleware = createReactNavigationReduxMiddleware; exports.createReduxBoundAddListener = createReduxBoundAddListener; exports.initializeListeners = initializeListeners; -},619,[31,620],"react-navigation-redux-helpers/src/middleware.js"); +},626,[31,627],"react-navigation-redux-helpers/src/middleware.js"); __d(function (global, require, module, exports, _dependencyMap) { Object.defineProperty(exports, "__esModule", { value: true @@ -75300,7 +76233,7 @@ __d(function (global, require, module, exports, _dependencyMap) { ; exports.createNavigationReducer = createNavigationReducer; exports.initAction = initAction; -},620,[376],"react-navigation-redux-helpers/src/reducer.js"); +},627,[376],"react-navigation-redux-helpers/src/reducer.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75361,7 +76294,7 @@ __d(function (global, require, module, exports, _dependencyMap) { exports.persistStore = _persistStore2.default; exports.purgeStoredState = _purgeStoredState2.default; exports.storages = storages; -},621,[622,625,630,631,632,628],"redux-persist/lib/index.js"); +},628,[629,632,637,638,639,635],"redux-persist/lib/index.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75463,14 +76396,14 @@ __d(function (global, require, module, exports, _dependencyMap) { }); return newState; } -},622,[623,624],"redux-persist/lib/autoRehydrate.js"); +},629,[630,631],"redux-persist/lib/autoRehydrate.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; exports.__esModule = true; var KEY_PREFIX = exports.KEY_PREFIX = 'reduxPersist:'; var REHYDRATE = exports.REHYDRATE = 'persist/REHYDRATE'; -},623,[],"redux-persist/lib/constants.js"); +},630,[],"redux-persist/lib/constants.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75501,7 +76434,7 @@ __d(function (global, require, module, exports, _dependencyMap) { if (!(0, _isPlainObject2.default)(a)) return false; return true; } -},624,[38],"redux-persist/lib/utils/isStatePlainEnough.js"); +},631,[38],"redux-persist/lib/utils/isStatePlainEnough.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75674,7 +76607,7 @@ __d(function (global, require, module, exports, _dependencyMap) { state[key] = value; return state; } -},625,[623,626,628,629],"redux-persist/lib/createPersistor.js"); +},632,[630,633,635,636],"redux-persist/lib/createPersistor.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75820,7 +76753,7 @@ __d(function (global, require, module, exports, _dependencyMap) { }; } } -},626,[627],"redux-persist/lib/defaults/asyncLocalStorage.js"); +},633,[634],"redux-persist/lib/defaults/asyncLocalStorage.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75832,7 +76765,7 @@ __d(function (global, require, module, exports, _dependencyMap) { return setTimeout(fn, ms); }; exports.default = setImmediate; -},627,[],"redux-persist/lib/utils/setImmediate.js"); +},634,[],"redux-persist/lib/utils/setImmediate.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -75876,7 +76809,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } }; } -},628,[623],"redux-persist/lib/purgeStoredState.js"); +},635,[630],"redux-persist/lib/purgeStoredState.js"); __d(function (global, require, module, exports, _dependencyMap) { exports = module.exports = stringify; exports.getSerialize = serializer; @@ -75903,7 +76836,7 @@ __d(function (global, require, module, exports, _dependencyMap) { return replacer == null ? value : replacer.call(this, key, value); }; } -},629,[],"json-stringify-safe/stringify.js"); +},636,[],"json-stringify-safe/stringify.js"); __d(function (global, require, module, exports, _dependencyMap) { "use strict"; @@ -75931,7 +76864,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } exports.default = createTransform; -},630,[],"redux-persist/lib/createTransform.js"); +},637,[],"redux-persist/lib/createTransform.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -76043,7 +76976,7 @@ __d(function (global, require, module, exports, _dependencyMap) { function defaultDeserializer(serial) { return JSON.parse(serial); } -},631,[623,626],"redux-persist/lib/getStoredState.js"); +},638,[630,633],"redux-persist/lib/getStoredState.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -76138,7 +77071,7 @@ __d(function (global, require, module, exports, _dependencyMap) { error: error }; } -},632,[623,631,625,627],"redux-persist/lib/persistStore.js"); +},639,[630,638,632,634],"redux-persist/lib/persistStore.js"); __d(function (global, require, module, exports, _dependencyMap) { "use strict"; @@ -76188,7 +77121,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } }, config); } -},633,[621,634,629],"redux-persist-transform-compress/lib/index.js"); +},640,[628,641,636],"redux-persist-transform-compress/lib/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var LZString = function () { var f = String.fromCharCode; @@ -76765,7 +77698,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } else if (typeof module !== 'undefined' && module != null) { module.exports = LZString; } -},634,[],"lz-string/libs/lz-string.js"); +},641,[],"lz-string/libs/lz-string.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -76897,7 +77830,7 @@ __d(function (global, require, module, exports, _dependencyMap) { return subset; } -},635,[621,636,637,638,639,640,641],"redux-persist-transform-filter/dist/index.js"); +},642,[628,643,644,645,646,647,648],"redux-persist-transform-filter/dist/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var FUNC_ERROR_TEXT = 'Expected a function'; var HASH_UNDEFINED = '__lodash_hash_undefined__'; @@ -77284,7 +78217,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = get; -},636,[],"lodash.get/index.js"); +},643,[],"lodash.get/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var FUNC_ERROR_TEXT = 'Expected a function'; var HASH_UNDEFINED = '__lodash_hash_undefined__'; @@ -77704,7 +78637,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = set; -},637,[],"lodash.set/index.js"); +},644,[],"lodash.set/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var FUNC_ERROR_TEXT = 'Expected a function'; var HASH_UNDEFINED = '__lodash_hash_undefined__'; @@ -78131,7 +79064,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = unset; -},638,[],"lodash.unset/index.js"); +},645,[],"lodash.unset/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var LARGE_ARRAY_SIZE = 200; var FUNC_ERROR_TEXT = 'Expected a function'; @@ -79293,7 +80226,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = pickBy; -},639,[],"lodash.pickby/index.js"); +},646,[],"lodash.pickby/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var MAX_SAFE_INTEGER = 9007199254740991; var argsTag = '[object Arguments]', @@ -79503,7 +80436,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = isEmpty; -},640,[],"lodash.isempty/index.js"); +},647,[],"lodash.isempty/index.js"); __d(function (global, require, module, exports, _dependencyMap) { var MAX_SAFE_INTEGER = 9007199254740991; var argsTag = '[object Arguments]', @@ -79647,7 +80580,7 @@ __d(function (global, require, module, exports, _dependencyMap) { } module.exports = forIn; -},641,[],"lodash.forin/index.js"); +},648,[],"lodash.forin/index.js"); __d(function (global, require, module, exports, _dependencyMap) { 'use strict'; @@ -79672,6 +80605,6 @@ __d(function (global, require, module, exports, _dependencyMap) { var thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; exports['default'] = thunk; -},642,[],"redux-thunk/lib/index.js"); +},649,[],"redux-thunk/lib/index.js"); require(76); require(11); \ No newline at end of file diff --git a/src/main/assets/index.android.bundle.meta b/src/main/assets/index.android.bundle.meta index 2563de6f..0e1bf48c 100644 --- a/src/main/assets/index.android.bundle.meta +++ b/src/main/assets/index.android.bundle.meta @@ -1 +1 @@ -JR['jÎÉà™©²ua Z \ No newline at end of file +·ƒ*Ö¿˜1r 8Š¤)×ò»E㊠\ No newline at end of file diff --git a/src/main/java/io/lbry/lbrynet/MainActivity.java b/src/main/java/io/lbry/lbrynet/MainActivity.java index ed135d86..a35c7b86 100644 --- a/src/main/java/io/lbry/lbrynet/MainActivity.java +++ b/src/main/java/io/lbry/lbrynet/MainActivity.java @@ -9,12 +9,15 @@ import android.content.Context; import android.net.Uri; import android.provider.Settings; +import com.brentvatne.react.ReactVideoPackage; import com.facebook.react.common.LifecycleState; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.ReactRootView; import com.facebook.react.ReactInstanceManager; import com.facebook.react.shell.MainReactPackage; +import io.lbry.lbrynet.reactpackages.LbryReactPackage; + public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { private static final int OVERLAY_PERMISSION_REQ_CODE = 101; @@ -51,7 +54,9 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand .setBundleAssetName("index.android.bundle") .setJSMainModulePath("index") .addPackage(new MainReactPackage()) - /*.setUseDeveloperSupport(BuildConfig.DEBUG)*/ + .addPackage(new ReactVideoPackage()) + .addPackage(new LbryReactPackage()) + .setUseDeveloperSupport(true) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null); diff --git a/src/main/java/io/lbry/lbrynet/reactmodules/LbryDownloadManagerModule.java b/src/main/java/io/lbry/lbrynet/reactmodules/LbryDownloadManagerModule.java new file mode 100644 index 00000000..509c923f --- /dev/null +++ b/src/main/java/io/lbry/lbrynet/reactmodules/LbryDownloadManagerModule.java @@ -0,0 +1,99 @@ +package io.lbry.lbrynet.reactmodules; + +import android.content.Context; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +import io.lbry.lbrynet.R; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Random; + +/** + * Created by akinwale on 3/15/18. + */ + +public class LbryDownloadManagerModule extends ReactContextBaseJavaModule { + private Context context; + + private HashMap builders = new HashMap(); + + private HashMap downloadIdNotificationIdMap = new HashMap(); + + private static final int MAX_PROGRESS = 100; + + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##"); + + public LbryDownloadManagerModule(ReactApplicationContext reactContext) { + super(reactContext); + this.context = reactContext; + } + + private int generateNotificationId() { + return new Random().nextInt(); + } + + @Override + public String getName() { + return "LbryDownloadManager"; + } + + @ReactMethod + public void startDownload(String id, String fileName) { + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setContentTitle(String.format("Downloading %s...", fileName)) + .setSmallIcon(R.drawable.ic_file_download_black_24dp) + .setPriority(NotificationCompat.PRIORITY_LOW); + + builder.setProgress(MAX_PROGRESS, 0, false); + + int notificationId = generateNotificationId(); + downloadIdNotificationIdMap.put(id, notificationId); + + builders.put(notificationId, builder); + notificationManager.notify(notificationId, builder.build()); + } + + @ReactMethod + public void updateDownload(String id, String fileName, double progress, double writtenBytes, double totalBytes) { + if (!downloadIdNotificationIdMap.containsKey(id)) { + return; + } + + int notificationId = downloadIdNotificationIdMap.get(id); + if (!builders.containsKey(notificationId)) { + return; + } + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + NotificationCompat.Builder builder = builders.get(notificationId); + builder.setProgress(MAX_PROGRESS, new Double(progress).intValue(), false); + builder.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes))); + notificationManager.notify(notificationId, builder.build()); + + if (progress == MAX_PROGRESS) { + builder.setContentTitle(String.format("Downloaded %s.", fileName)); + downloadIdNotificationIdMap.remove(id); + builders.remove(notificationId); + } + } + + private String formatBytes(double bytes) + { + if (bytes < 1048576) { // < 1MB + return String.format("%s KB", DECIMAL_FORMAT.format(bytes / 1024.0)); + } + + if (bytes < 1073741824) { // < 1GB + return String.format("%s MB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0))); + } + + return String.format("%s GB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0 * 1024.0))); + } +} diff --git a/src/main/java/io/lbry/lbrynet/reactpackages/LbryReactPackage.java b/src/main/java/io/lbry/lbrynet/reactpackages/LbryReactPackage.java new file mode 100644 index 00000000..2d9c289c --- /dev/null +++ b/src/main/java/io/lbry/lbrynet/reactpackages/LbryReactPackage.java @@ -0,0 +1,28 @@ +package io.lbry.lbrynet.reactpackages; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import io.lbry.lbrynet.reactmodules.LbryDownloadManagerModule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LbryReactPackage implements ReactPackage { + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new LbryDownloadManagerModule(reactContext)); + + return modules; + } +} \ No newline at end of file