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 00000000..d9aacea4 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png differ 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 00000000..c2c845e8 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png differ 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 00000000..f5afb24d Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png new file mode 100644 index 00000000..ce97c85d Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png differ 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 00000000..8c83bffa Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png differ 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 00000000..d9aacea4 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png differ 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 00000000..d50bdaae Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png differ 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 00000000..c2c845e8 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png differ 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 00000000..0a299eb3 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png new file mode 100644 index 00000000..f5afb24d Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png differ diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..a336ad5c Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png differ 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 00000000..ce97c85d Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png differ 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 00000000..d423dac2 Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png differ 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 00000000..8c83bffa Binary files /dev/null and b/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png differ 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