Playable downloads #111

Merged
akinwale merged 7 commits from playable-downloads into master 2020-01-16 22:00:34 +01:00
20 changed files with 129 additions and 82 deletions

View file

@ -10,6 +10,7 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc'; import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import ClaimResultItem from './view'; import ClaimResultItem from './view';
@ -32,6 +33,7 @@ const select = (state, props) => ({
const perform = dispatch => ({ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
}); });
export default connect( export default connect(

View file

@ -39,14 +39,14 @@ class ClaimResultItem extends React.PureComponent {
} }
onPressHandler = () => { onPressHandler = () => {
const { autoplay, navigation, result } = this.props; const { autoplay, navigation, result, setPlayerVisible } = this.props;
const { claimId, name } = result; const { claimId, name } = result;
const url = normalizeURI(`${name}#${claimId}`); const url = normalizeURI(`${name}#${claimId}`);
navigateToUri(navigation, url, { autoplay }, false, url); navigateToUri(navigation, url, { autoplay }, false, url, setPlayerVisible);
}; };
render() { render() {
const { fileInfo, navigation, obscureNsfw, result, rewardedContentClaimIds, style } = this.props; const { fileInfo, navigation, obscureNsfw, result, rewardedContentClaimIds, setPlayerVisible, style } = this.props;
const { const {
channel, channel,
channel_claim_id: channelClaimId, channel_claim_id: channelClaimId,
@ -134,6 +134,7 @@ class ClaimResultItem extends React.PureComponent {
null, null,
false, false,
isChannel ? url : channelUrl, isChannel ? url : channelUrl,
setPlayerVisible,
); );
}} }}
/> />
@ -141,7 +142,7 @@ class ClaimResultItem extends React.PureComponent {
<View style={fileListStyle.info}> <View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && ( {fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text>{getStorageForFileInfo(fileInfo)}</Text> <Text style={fileListStyle.infoText}>{getStorageForFileInfo(fileInfo)}</Text>
)} )}
<DateTime <DateTime
style={fileListStyle.publishInfo} style={fileListStyle.publishInfo}

View file

@ -10,6 +10,7 @@ import {
makeSelectClaimIsNsfw, makeSelectClaimIsNsfw,
makeSelectShortUrlForUri, makeSelectShortUrlForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc'; import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import FileItem from './view'; import FileItem from './view';
@ -31,9 +32,10 @@ const select = (state, props) => ({
const perform = dispatch => ({ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
}); });
export default connect( export default connect(
select, select,
perform perform,
)(FileItem); )(FileItem);

View file

@ -56,6 +56,7 @@ class FileItem extends React.PureComponent {
obscureNsfw, obscureNsfw,
showDetails, showDetails,
compactView, compactView,
setPlayerVisible,
titleBeforeThumbnail, titleBeforeThumbnail,
} = this.props; } = this.props;
@ -135,6 +136,7 @@ class FileItem extends React.PureComponent {
null, null,
false, false,
fullChannelUri, fullChannelUri,
setPlayerVisible,
); );
}} }}
/> />

View file

@ -10,6 +10,7 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc'; import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import FileListItem from './view'; import FileListItem from './view';
@ -32,6 +33,7 @@ const select = (state, props) => ({
const perform = dispatch => ({ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
}); });
export default connect( export default connect(

View file

@ -80,6 +80,7 @@ class FileListItem extends React.PureComponent {
onPress, onPress,
navigation, navigation,
rewardedContentClaimIds, rewardedContentClaimIds,
setPlayerVisible,
thumbnail, thumbnail,
hideChannel, hideChannel,
onLongPress, onLongPress,
@ -236,6 +237,7 @@ class FileListItem extends React.PureComponent {
null, null,
false, false,
isChannel ? claim && claim.permanent_url : fullChannelUri, isChannel ? claim && claim.permanent_url : fullChannelUri,
setPlayerVisible,
); );
}} }}
/> />

View file

@ -32,6 +32,7 @@ export default class RelatedContent extends React.PureComponent {
recommendedContent.map(result => ( recommendedContent.map(result => (
<ClaimResultItem <ClaimResultItem
style={fileListStyle.item} style={fileListStyle.item}
uri={result ? normalizeURI(`${result.name}#${result.claimId}`) : null}
key={result.claimId} key={result.claimId}
result={result} result={result}
navigation={navigation} navigation={navigation}

View file

@ -6,6 +6,7 @@ import {
selectSearchSuggestions, selectSearchSuggestions,
SETTINGS, SETTINGS,
} from 'lbry-redux'; } from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer'; import { selectCurrentRoute } from 'redux/selectors/drawer';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import UriBar from './view'; import UriBar from './view';
@ -24,9 +25,10 @@ const select = state => {
const perform = dispatch => ({ const perform = dispatch => ({
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)), updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
}); });
export default connect( export default connect(
select, select,
perform perform,
)(UriBar); )(UriBar);

View file

@ -83,7 +83,7 @@ class UriBar extends React.PureComponent {
}; };
handleItemPress = item => { handleItemPress = item => {
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props; const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props;
const { type, value } = item; const { type, value } = item;
Keyboard.dismiss(); Keyboard.dismiss();
@ -112,7 +112,7 @@ class UriBar extends React.PureComponent {
}); });
} else { } else {
const uri = normalizeURI(value); const uri = normalizeURI(value);
navigateToUri(navigation, uri); navigateToUri(navigation, uri, null, false, null, setPlayerVisible);
} }
}; };
@ -128,7 +128,7 @@ class UriBar extends React.PureComponent {
}; };
handleSubmitEditing = () => { handleSubmitEditing = () => {
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props; const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props;
if (this.state.inputText) { if (this.state.inputText) {
let inputText = this.state.inputText, let inputText = this.state.inputText,
inputTextIsUrl = false; inputTextIsUrl = false;
@ -137,7 +137,7 @@ class UriBar extends React.PureComponent {
// if it's a URI (lbry://...), open the file page // if it's a URI (lbry://...), open the file page
if (transformedUrl && isURIValid(transformedUrl)) { if (transformedUrl && isURIValid(transformedUrl)) {
inputTextIsUrl = true; inputTextIsUrl = true;
navigateToUri(navigation, transformedUrl); navigateToUri(navigation, transformedUrl, null, false, null, setPlayerVisible);
} }
} }

View file

@ -11,7 +11,7 @@ import {
selectSubscriptionClaims, selectSubscriptionClaims,
selectUnreadSubscriptions, selectUnreadSubscriptions,
} from 'lbryinc'; } from 'lbryinc';
import { doPushDrawerStack } from 'redux/actions/drawer'; import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings'; import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
import { makeSelectClientSetting, selectSortByItem, selectTimeItem } from 'redux/selectors/settings'; import { makeSelectClientSetting, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -41,11 +41,12 @@ const perform = dispatch => ({
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_DISCOVER)), pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_DISCOVER)),
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()), removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
setSortByItem: item => dispatch(doSetSortByItem(item)), setSortByItem: item => dispatch(doSetSortByItem(item)),
setTimeItem: item => dispatch(doSetTimeItem(item)), setTimeItem: item => dispatch(doSetTimeItem(item)),
}); });
export default connect( export default connect(
select, select,
perform perform,
)(DiscoverPage); )(DiscoverPage);

View file

@ -86,9 +86,11 @@ class DiscoverPage extends React.PureComponent {
} }
onComponentFocused = () => { onComponentFocused = () => {
const { pushDrawerStack } = this.props; const { pushDrawerStack, setPlayerVisible } = this.props;
// pushDrawerStack(); // pushDrawerStack();
NativeModules.Firebase.setCurrentScreen('Your tags'); NativeModules.Firebase.setCurrentScreen('Your tags');
setPlayerVisible();
}; };
handleSortByItemSelected = item => { handleSortByItemSelected = item => {
@ -135,7 +137,7 @@ class DiscoverPage extends React.PureComponent {
Alert.alert( Alert.alert(
__('Enjoying LBRY?'), __('Enjoying LBRY?'),
__( __(
'Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.' 'Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.',
), ),
[ [
{ {
@ -151,7 +153,7 @@ class DiscoverPage extends React.PureComponent {
}, },
}, },
], ],
{ cancelable: false } { cancelable: false },
); );
} }
} }

View file

@ -79,7 +79,7 @@ class DownloadsPage extends React.PureComponent {
const { claims, fileInfos } = this.props; const { claims, fileInfos } = this.props;
const claimUris = claims.map(claim => normalizeURI(`${claim.name}#${claim.claim_id}`)); const claimUris = claims.map(claim => normalizeURI(`${claim.name}#${claim.claim_id}`));
return fileInfos.filter( return fileInfos.filter(
fileInfo => !claimUris.includes(normalizeURI(`${fileInfo.claim_name}#${fileInfo.claim_id}`)) fileInfo => !claimUris.includes(normalizeURI(`${fileInfo.claim_name}#${fileInfo.claim_id}`)),
); );
}; };
@ -135,7 +135,7 @@ class DownloadsPage extends React.PureComponent {
], ],
{ {
cancelable: true, cancelable: true,
} },
); );
}; };
@ -184,7 +184,7 @@ class DownloadsPage extends React.PureComponent {
this.handleSelectItem(item, claim); this.handleSelectItem(item, claim);
} else { } else {
// TODO: when shortUrl is available for my claims, navigate to that URL instead // TODO: when shortUrl is available for my claims, navigate to that URL instead
navigateToUri(navigation, item, { autoplay: true }); navigateToUri(navigation, item, { autoplay: true }, false, null);
} }
}} }}
onLongPress={claim => this.handleItemLongPress(item, claim)} onLongPress={claim => this.handleItemLongPress(item, claim)}

View file

@ -41,7 +41,7 @@ import {
import { doDeleteFile, doStopDownloadingFile } from 'redux/actions/file'; import { doDeleteFile, doStopDownloadingFile } from 'redux/actions/file';
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doToggleFullscreenMode } from 'redux/actions/settings'; import { doToggleFullscreenMode } from 'redux/actions/settings';
import { selectDrawerStack, selectIsPlayerVisible } from 'redux/selectors/drawer'; import { selectDrawerStack, makeSelectPlayerVisible } from 'redux/selectors/drawer';
import FilePage from './view'; import FilePage from './view';
const select = (state, props) => { const select = (state, props) => {
@ -61,7 +61,7 @@ const select = (state, props) => {
fileInfo: makeSelectFileInfoForUri(contentUri)(state), fileInfo: makeSelectFileInfoForUri(contentUri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps), rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps),
channelUri: makeSelectChannelForClaimUri(contentUri, true)(state), channelUri: makeSelectChannelForClaimUri(contentUri, true)(state),
isPlayerVisible: selectIsPlayerVisible(state), isPlayerVisible: makeSelectPlayerVisible(uri)(state), // use navigation uri for this selector
position: makeSelectContentPositionForUri(contentUri)(state), position: makeSelectContentPositionForUri(contentUri)(state),
purchasedUris: selectPurchasedUris(state), purchasedUris: selectPurchasedUris(state),
failedPurchaseUris: selectFailedPurchaseUris(state), failedPurchaseUris: selectFailedPurchaseUris(state),
@ -96,7 +96,7 @@ const perform = dispatch => ({
resolveUris: uris => dispatch(doResolveUris(uris)), resolveUris: uris => dispatch(doResolveUris(uris)),
sendTip: (amount, claimId, isSupport, successCallback, errorCallback) => sendTip: (amount, claimId, isSupport, successCallback, errorCallback) =>
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)), dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)), setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)), stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)), toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)),
}); });

View file

@ -94,6 +94,7 @@ class FilePage extends React.PureComponent {
showImageViewer: false, showImageViewer: false,
showWebView: false, showWebView: false,
showTipView: false, showTipView: false,
playbackStarted: false,
playerBgHeight: 0, playerBgHeight: 0,
playerHeight: 0, playerHeight: 0,
uri: null, uri: null,
@ -115,6 +116,8 @@ class FilePage extends React.PureComponent {
onComponentFocused = () => { onComponentFocused = () => {
StatusBar.setHidden(false); StatusBar.setHidden(false);
NativeModules.Firebase.setCurrentScreen('File').then(result => { NativeModules.Firebase.setCurrentScreen('File').then(result => {
const { setPlayerVisible } = this.props;
DeviceEventEmitter.addListener('onStoragePermissionGranted', this.handleStoragePermissionGranted); DeviceEventEmitter.addListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
DeviceEventEmitter.addListener('onStoragePermissionRefused', this.handleStoragePermissionRefused); DeviceEventEmitter.addListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
@ -122,6 +125,7 @@ class FilePage extends React.PureComponent {
const { uri, uriVars } = navigation.state.params; const { uri, uriVars } = navigation.state.params;
this.setState({ uri, uriVars }); this.setState({ uri, uriVars });
setPlayerVisible(true, uri);
if (!isResolvingUri && !claim) resolveUri(uri); if (!isResolvingUri && !claim) resolveUri(uri);
this.fetchFileInfo(uri, this.props); this.fetchFileInfo(uri, this.props);
@ -162,7 +166,6 @@ class FilePage extends React.PureComponent {
notify, notify,
drawerStack: prevDrawerStack, drawerStack: prevDrawerStack,
} = this.props; } = this.props;
const { uri } = navigation.state.params;
const { const {
currentRoute: prevRoute, currentRoute: prevRoute,
failedPurchaseUris, failedPurchaseUris,
@ -173,6 +176,7 @@ class FilePage extends React.PureComponent {
drawerStack, drawerStack,
resolveUris, resolveUris,
} = nextProps; } = nextProps;
const uri = this.getPurchaseUrl();
if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) { if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) {
this.onComponentFocused(); this.onComponentFocused();
@ -206,7 +210,7 @@ class FilePage extends React.PureComponent {
NativeModules.UtilityModule.checkDownloads(); NativeModules.UtilityModule.checkDownloads();
} }
if (!this.state.streamingMode && isPlayable) { if ((!fileInfo || (fileInfo && !fileInfo.completed)) && !this.state.streamingMode && isPlayable) {
if (streamingUrl) { if (streamingUrl) {
this.setState({ streamingMode: true, currentStreamUrl: streamingUrl }); this.setState({ streamingMode: true, currentStreamUrl: streamingUrl });
} else if (fileInfo && fileInfo.streaming_url) { } else if (fileInfo && fileInfo.streaming_url) {
@ -231,6 +235,9 @@ class FilePage extends React.PureComponent {
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { fileInfo: prevFileInfo } = this.props;
const { fileInfo } = nextProps;
return ( return (
Object.keys(this.difference(nextProps, this.props)).length > 0 || Object.keys(this.difference(nextProps, this.props)).length > 0 ||
Object.keys(this.difference(nextState, this.state)).length > 0 Object.keys(this.difference(nextState, this.state)).length > 0
@ -368,14 +375,14 @@ class FilePage extends React.PureComponent {
const { deletePurchasedUri, fileInfo, navigation, notify, stopDownload } = this.props; const { deletePurchasedUri, fileInfo, navigation, notify, stopDownload } = this.props;
Alert.alert( Alert.alert(
'Stop download', __('Stop download'),
'Are you sure you want to stop downloading this file?', __('Are you sure you want to stop downloading this file?'),
[ [
{ text: 'No' }, { text: __('No') },
{ {
text: 'Yes', text: __('Yes'),
onPress: () => { onPress: () => {
const { uri } = navigation.state.params; const uri = this.getPurchaseUrl();
stopDownload(uri, fileInfo); stopDownload(uri, fileInfo);
deletePurchasedUri(uri); deletePurchasedUri(uri);
if (NativeModules.UtilityModule) { if (NativeModules.UtilityModule) {
@ -391,7 +398,7 @@ class FilePage extends React.PureComponent {
// there can be a bit of lag between the user pressing Yes and the UI being updated // there can be a bit of lag between the user pressing Yes and the UI being updated
// after the file_set_status and file_delete operations, so let the user know // after the file_set_status and file_delete operations, so let the user know
notify({ notify({
message: 'The download will stop momentarily. You do not need to wait to discover something else.', message: __('The download will stop momentarily. You do not need to wait to discover something else.'),
}); });
}, },
}, },
@ -416,8 +423,6 @@ class FilePage extends React.PureComponent {
if (window.currentMediaInfo) { if (window.currentMediaInfo) {
window.currentMediaInfo = null; window.currentMediaInfo = null;
} }
window.player = null;
DeviceEventEmitter.removeListener('onStoragePermissionGranted', this.handleStoragePermissionGranted); DeviceEventEmitter.removeListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
DeviceEventEmitter.removeListener('onStoragePermissionRefused', this.handleStoragePermissionRefused); DeviceEventEmitter.removeListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
} }
@ -434,7 +439,7 @@ class FilePage extends React.PureComponent {
}) })
.then(() => this.performDownload()) .then(() => this.performDownload())
.catch(() => { .catch(() => {
notify({ message: 'The file could not be downloaded to the default download directory.', isError: true }); notify({ message: __('The file could not be downloaded to the default download directory.'), isError: true });
}); });
}); });
}; };
@ -452,12 +457,14 @@ class FilePage extends React.PureComponent {
if (!fileInfo) { if (!fileInfo) {
return null; return null;
} }
return 'file:///' + fileInfo.download_path; return 'file://' + fileInfo.download_path;
}; };
playerUriForFileInfo = fileInfo => { playerUriForFileInfo = fileInfo => {
const { streamingUrl } = this.props; const { streamingUrl } = this.props;
if (fileInfo && fileInfo.download_path) { if (!this.state.streamingMode && fileInfo && fileInfo.download_path && fileInfo.completed) {
// take streamingMode in the state into account because if the download completes while
// the media is already streaming, it will restart from the beginning
return this.getEncodedDownloadPath(fileInfo); return this.getEncodedDownloadPath(fileInfo);
} }
if (streamingUrl) { if (streamingUrl) {
@ -564,7 +571,7 @@ class FilePage extends React.PureComponent {
NativeModules.Firebase.track('play', payload); NativeModules.Firebase.track('play', payload);
// only fetch recommended content after playback has started // only fetch recommended content after playback has started
this.setState({ showRecommended: true }); this.setState({ playbackStarted: true, showRecommended: true });
}; };
onPlaybackFinished = () => { onPlaybackFinished = () => {
@ -668,7 +675,7 @@ class FilePage extends React.PureComponent {
if (isPlayable) { if (isPlayable) {
this.startTime = Date.now(); this.startTime = Date.now();
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false }); this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
setPlayerVisible(); // setPlayerVisible(purchaseUrl);
} }
if (isViewable) { if (isViewable) {
this.setState({ downloadPressed: true }); this.setState({ downloadPressed: true });
@ -682,7 +689,7 @@ class FilePage extends React.PureComponent {
let purchaseUrl; let purchaseUrl;
if (navigation.state.params) { if (navigation.state.params) {
const { uri, fullUri } = navigation.state.params; const { uri, fullUri } = navigation.state.params;
purchaseUrl = fullUri || uri || permanentUrl; purchaseUrl = fullUri || permanentUrl || uri;
} }
if (!purchaseUrl && permanentUrl) { if (!purchaseUrl && permanentUrl) {
purchaseUrl = permanentUrl; purchaseUrl = permanentUrl;
@ -708,7 +715,7 @@ class FilePage extends React.PureComponent {
}; };
performDownload = () => { performDownload = () => {
const { claim, costInfo, purchaseUri } = this.props; const { claim, costInfo, fileGet, fileInfo, purchasedUris } = this.props;
this.setState( this.setState(
{ {
downloadPressed: true, downloadPressed: true,
@ -716,15 +723,21 @@ class FilePage extends React.PureComponent {
stopDownloadConfirmed: false, stopDownloadConfirmed: false,
}, },
() => { () => {
this.confirmPurchaseUri(claim.permanent_url, costInfo, true); const url = this.getPurchaseUrl();
if (fileInfo || purchasedUris.includes(url)) {
// file already in library or URI already purchased, use fileGet directly
this.setState({ fileGetStarted: true }, () => fileGet(url, true));
} else {
this.confirmPurchaseUri(url, costInfo, true);
}
NativeModules.UtilityModule.checkDownloads(); NativeModules.UtilityModule.checkDownloads();
}, },
); );
}; };
onBackButtonPressed = () => { onBackButtonPressed = () => {
const { navigation, drawerStack, popDrawerStack } = this.props; const { navigation, drawerStack, popDrawerStack, setPlayerVisible } = this.props;
navigateBack(navigation, drawerStack, popDrawerStack); navigateBack(navigation, drawerStack, popDrawerStack, setPlayerVisible);
}; };
onOpenFilePressed = () => { onOpenFilePressed = () => {
@ -732,25 +745,16 @@ class FilePage extends React.PureComponent {
const localFileUri = this.localUriForFileInfo(fileInfo); const localFileUri = this.localUriForFileInfo(fileInfo);
const mediaType = Lbry.getMediaType(contentType); const mediaType = Lbry.getMediaType(contentType);
const isViewable = mediaType === 'image' || mediaType === 'text'; const isViewable = mediaType === 'image' || mediaType === 'text';
const isPlayable = mediaType === 'video' || mediaType === 'audio';
if (isViewable) { if (isViewable) {
this.openFile(localFileUri, mediaType, contentType); this.openFile(localFileUri, mediaType, contentType);
} else if (isPlayable) {
notify({ message: __('Please press the Play button.') });
} else { } else {
notify({ message: __('This file cannot be displayed in the LBRY app.') }); notify({ message: __('This file cannot be displayed in the LBRY app.') });
} }
}; };
onSaveFilePressed = () => {
const { costInfo, fileGet, fileInfo, navigation, purchasedUris, purchaseUri } = this.props;
const { uri } = navigation.state.params;
if (fileInfo || purchasedUris.includes(uri)) {
// file already in library or URI already purchased, use fileGet directly
this.setState({ fileGetStarted: true }, () => fileGet(uri, true));
} else {
this.checkStoragePermissionForDownload();
}
};
openFile = (localFileUri, mediaType, contentType) => { openFile = (localFileUri, mediaType, contentType) => {
const { pushDrawerStack } = this.props; const { pushDrawerStack } = this.props;
const isWebViewable = mediaType === 'text'; const isWebViewable = mediaType === 'text';
@ -880,6 +884,7 @@ class FilePage extends React.PureComponent {
position, position,
purchaseUri, purchaseUri,
pushDrawerStack, pushDrawerStack,
setPlayerVisible,
thumbnail, thumbnail,
title, title,
viewCount, viewCount,
@ -1199,7 +1204,7 @@ class FilePage extends React.PureComponent {
<Text style={filePageStyle.largeButtonText}>{__('Tip')}</Text> <Text style={filePageStyle.largeButtonText}>{__('Tip')}</Text>
</TouchableOpacity> </TouchableOpacity>
{!canEdit && !isPlayable && ( {!canEdit && (
<View style={filePageStyle.sharedLargeButton}> <View style={filePageStyle.sharedLargeButton}>
{(!fileInfo || (fileInfo.written_bytes <= 0 && !completed)) && ( {(!fileInfo || (fileInfo.written_bytes <= 0 && !completed)) && (
<TouchableOpacity style={filePageStyle.innerLargeButton} onPress={this.onDownloadPressed}> <TouchableOpacity style={filePageStyle.innerLargeButton} onPress={this.onDownloadPressed}>
@ -1210,7 +1215,6 @@ class FilePage extends React.PureComponent {
{!completed && {!completed &&
fileInfo && fileInfo &&
!fileInfo.stopped &&
fileInfo.written_bytes > 0 && fileInfo.written_bytes > 0 &&
fileInfo.written_bytes < fileInfo.total_bytes && fileInfo.written_bytes < fileInfo.total_bytes &&
!this.state.stopDownloadConfirmed && ( !this.state.stopDownloadConfirmed && (
@ -1270,6 +1274,7 @@ class FilePage extends React.PureComponent {
null, null,
false, false,
fullChannelUri, fullChannelUri,
setPlayerVisible,
); );
}} }}
/> />
@ -1288,14 +1293,6 @@ class FilePage extends React.PureComponent {
/> />
</View> </View>
<View style={filePageStyle.subscriptionRow}> <View style={filePageStyle.subscriptionRow}>
{false && ((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
<Button
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
theme={'light'}
icon={'download'}
onPress={this.onSaveFilePressed}
/>
)}
{channelName && ( {channelName && (
<SubscribeButton <SubscribeButton
style={filePageStyle.actionButton} style={filePageStyle.actionButton}

View file

@ -239,6 +239,7 @@ class SearchPage extends React.PureComponent {
renderItem={({ item }) => ( renderItem={({ item }) => (
<ClaimResultItem <ClaimResultItem
key={item.claimId} key={item.claimId}
uri={item ? normalizeURI(`${item.name}#${item.claimId}`) : null}
result={item} result={item}
style={searchStyle.resultItem} style={searchStyle.resultItem}
navigation={navigation} navigation={navigation}

View file

@ -11,8 +11,8 @@ export const doPopDrawerStack = () => dispatch =>
type: Constants.ACTION_POP_DRAWER_STACK, type: Constants.ACTION_POP_DRAWER_STACK,
}); });
export const doSetPlayerVisible = visible => dispatch => export const doSetPlayerVisible = (visible, uri) => dispatch =>
dispatch({ dispatch({
type: Constants.ACTION_SET_PLAYER_VISIBLE, type: Constants.ACTION_SET_PLAYER_VISIBLE,
data: { visible }, data: { visible, uri },
}); });

View file

@ -4,13 +4,27 @@ const reducers = {};
const defaultState = { const defaultState = {
stack: [{ route: Constants.DRAWER_ROUTE_DISCOVER, params: {} }], // Discover is always the first drawer route stack: [{ route: Constants.DRAWER_ROUTE_DISCOVER, params: {} }], // Discover is always the first drawer route
playerVisible: false, playerVisible: false,
playerVisibleByUri: {},
currentRoute: null, currentRoute: null,
}; };
reducers[Constants.ACTION_SET_PLAYER_VISIBLE] = (state, action) => reducers[Constants.ACTION_SET_PLAYER_VISIBLE] = (state, action) => {
Object.assign({}, state, { const { visible, uri } = action.data;
playerVisible: action.data.visible, const playerVisibleByUri = Object.assign({}, state.playerVisibleByUri);
if (!uri) {
Object.keys(playerVisibleByUri).forEach(playerUri => {
playerVisibleByUri[playerUri] = visible;
}); });
} else {
playerVisibleByUri[uri] = visible;
}
return Object.assign({}, state, {
playerVisible: action.data.visible,
playerVisibleByUri,
});
};
reducers[Constants.ACTION_PUSH_DRAWER_STACK] = (state, action) => { reducers[Constants.ACTION_PUSH_DRAWER_STACK] = (state, action) => {
const { routeName, params } = action.data; const { routeName, params } = action.data;

View file

@ -4,12 +4,23 @@ export const selectState = state => state.drawer || {};
export const selectDrawerStack = createSelector( export const selectDrawerStack = createSelector(
selectState, selectState,
state => state.stack state => state.stack,
); );
export const selectIsPlayerVisible = createSelector( export const selectIsPlayerVisible = createSelector(
selectState, selectState,
state => state.playerVisible state => state.playerVisible,
);
export const selectPlayerVisibleByUri = createSelector(
selectState,
state => state.playerVisibleByUri,
);
export const makeSelectPlayerVisible = uri =>
createSelector(
selectPlayerVisibleByUri,
byUri => (byUri ? byUri[uri] : false),
); );
export const selectLastDrawerRoute = createSelector( export const selectLastDrawerRoute = createSelector(
@ -20,10 +31,10 @@ export const selectLastDrawerRoute = createSelector(
} }
return null; return null;
} },
); );
export const selectCurrentRoute = createSelector( export const selectCurrentRoute = createSelector(
selectState, selectState,
state => state.currentRoute state => state.currentRoute,
); );

View file

@ -403,7 +403,7 @@ const filePageStyle = StyleSheet.create({
}, },
largeButtonText: { largeButtonText: {
fontFamily: 'Inter-Regular', fontFamily: 'Inter-Regular',
fontSize: 14, fontSize: 12,
marginTop: 4, marginTop: 4,
}, },
largeButtonsRow: { largeButtonsRow: {

View file

@ -54,9 +54,10 @@ export function dispatchNavigateToUri(dispatch, nav, uri, isNavigatingBack, full
if (!isNavigatingBack) { if (!isNavigatingBack) {
dispatch(doPushDrawerStack(uri)); dispatch(doPushDrawerStack(uri));
dispatch(doSetPlayerVisible(true));
} }
dispatch(doSetPlayerVisible(false));
if (nav && nav.routes && nav.routes.length > 0 && nav.routes[0].routeName === 'Main') { if (nav && nav.routes && nav.routes.length > 0 && nav.routes[0].routeName === 'Main') {
const mainRoute = nav.routes[0]; const mainRoute = nav.routes[0];
const discoverRoute = mainRoute.routes[0]; const discoverRoute = mainRoute.routes[0];
@ -64,14 +65,14 @@ export function dispatchNavigateToUri(dispatch, nav, uri, isNavigatingBack, full
const fileRoute = discoverRoute.routes[discoverRoute.index]; const fileRoute = discoverRoute.routes[discoverRoute.index];
// Currently on a file page, so we can ignore (if the URI is the same) or replace (different URIs) // Currently on a file page, so we can ignore (if the URI is the same) or replace (different URIs)
if (uri !== fileRoute.params.uri) { if (uri !== fileRoute.params.uri) {
const stackAction = StackActions.replace({ routeName: 'File', newKey: 'file', params }); const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
dispatch(stackAction); dispatch(stackAction);
return; return;
} }
} }
} }
const navigateAction = NavigationActions.navigate({ routeName: 'File', key: 'file', params }); const navigateAction = NavigationActions.navigate({ routeName: 'File', key: uri, params });
dispatch(navigateAction); dispatch(navigateAction);
} }
@ -113,7 +114,7 @@ function parseUriVars(vars) {
return uriVars; return uriVars;
} }
export function navigateToUri(navigation, uri, additionalParams, isNavigatingBack, fullUri) { export function navigateToUri(navigation, uri, additionalParams, isNavigatingBack, fullUri, setPlayerVisible) {
if (!navigation) { if (!navigation) {
return; return;
} }
@ -135,26 +136,28 @@ export function navigateToUri(navigation, uri, additionalParams, isNavigatingBac
uriVars = parseUriVars(uriVarsStr); uriVars = parseUriVars(uriVarsStr);
} }
if (setPlayerVisible) {
setPlayerVisible(false);
}
const { store } = window; const { store } = window;
const params = Object.assign({ uri, uriVars, fullUri: fullUri }, additionalParams); const params = Object.assign({ uri, uriVars, fullUri: fullUri }, additionalParams);
if (navigation.state.routeName === 'File') { if (navigation.state.routeName === 'File') {
const stackAction = StackActions.replace({ routeName: 'File', newKey: 'file', params }); const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
navigation.dispatch(stackAction); navigation.dispatch(stackAction);
if (store && store.dispatch && !isNavigatingBack) { if (store && store.dispatch && !isNavigatingBack) {
store.dispatch(doPushDrawerStack(uri)); store.dispatch(doPushDrawerStack(uri));
store.dispatch(doSetPlayerVisible(true));
} }
return; return;
} }
navigation.navigate({ routeName: 'File', key: 'file', params }); navigation.navigate({ routeName: 'File', key: uri, params });
if (store && store.dispatch && !isNavigatingBack) { if (store && store.dispatch && !isNavigatingBack) {
store.dispatch(doPushDrawerStack(uri)); store.dispatch(doPushDrawerStack(uri));
store.dispatch(doSetPlayerVisible(true));
} }
} }
export function navigateBack(navigation, drawerStack, popDrawerStack) { export function navigateBack(navigation, drawerStack, popDrawerStack, setPlayerVisible) {
if (drawerStack[drawerStack.length - 1].route === Constants.DRAWER_ROUTE_FILE_VIEW) { if (drawerStack[drawerStack.length - 1].route === Constants.DRAWER_ROUTE_FILE_VIEW) {
// inner file_view (web / image view) is handled differently // inner file_view (web / image view) is handled differently
if (popDrawerStack) { if (popDrawerStack) {
@ -166,12 +169,15 @@ export function navigateBack(navigation, drawerStack, popDrawerStack) {
if (popDrawerStack) { if (popDrawerStack) {
popDrawerStack(); popDrawerStack();
} }
if (setPlayerVisible) {
setPlayerVisible(false);
}
const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0]; const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0];
const { route, params } = target; const { route, params } = target;
navigation.goBack(navigation.state.key); navigation.goBack(navigation.state.key);
if (!DrawerRoutes.includes(route) && !InnerDrawerRoutes.includes(route) && isURIValid(route)) { if (!DrawerRoutes.includes(route) && !InnerDrawerRoutes.includes(route) && isURIValid(route)) {
navigateToUri(navigation, route, null, true); navigateToUri(navigation, route, null, true, null, setPlayerVisible);
} else { } else {
let targetRoute = route; let targetRoute = route;
let targetParams = params; let targetParams = params;
@ -201,6 +207,7 @@ export function dispatchNavigateBack(dispatch, nav, drawerStack) {
} }
dispatch(doPopDrawerStack()); dispatch(doPopDrawerStack());
dispatch(doSetPlayerVisible(false));
const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0]; const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0];
const { route } = target; const { route } = target;