edit published content

This commit is contained in:
Akinwale Ariwodola 2019-08-22 15:51:57 +01:00
parent 1e31d8d22b
commit 330997bc1e
11 changed files with 182 additions and 45 deletions

6
package-lock.json generated
View file

@ -8046,9 +8046,9 @@
} }
}, },
"react-native-document-picker": { "react-native-document-picker": {
"version": "2.3.0", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.3.0.tgz", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-3.2.4.tgz",
"integrity": "sha512-bHMyAOzFl+II0ZdfzobKsZKvTErmXfmQGalpxpGbeN8+/uhfhUcdp4WuIMecZhFyX6rbj3h3XXLdA12hVlGgmw==" "integrity": "sha512-5l0/fkgasUZdIk9jUUkReDtNCQn2yg1+BrMPHMt45c/NVmE15ThnhIuDj8/n8h1F1RlhUb3SzF86ANK4OdZAiQ=="
}, },
"react-native-exception-handler": { "react-native-exception-handler": {
"version": "2.9.0", "version": "2.9.0",

View file

@ -22,7 +22,7 @@
"@react-native-community/async-storage": "^1.5.1", "@react-native-community/async-storage": "^1.5.1",
"react-native-camera": "^2.11.1", "react-native-camera": "^2.11.1",
"react-native-country-picker-modal": "^0.6.2", "react-native-country-picker-modal": "^0.6.2",
"react-native-document-picker": "^2.3.0", "react-native-document-picker": "^3.2.4",
"react-native-exception-handler": "2.9.0", "react-native-exception-handler": "2.9.0",
"react-native-fast-image": "^6.1.1", "react-native-fast-image": "^6.1.1",
"react-native-fs": "^2.13.3", "react-native-fs": "^2.13.3",

View file

@ -26,12 +26,19 @@ export default class ChannelSelector extends React.PureComponent {
} }
componentDidMount() { componentDidMount() {
const { channels, fetchChannelListMine, fetchingChannels } = this.props; const { channels, channelName, fetchChannelListMine, fetchingChannels } = this.props;
if (!channels.length && !fetchingChannels) { if (!channels.length && !fetchingChannels) {
fetchChannelListMine(); fetchChannelListMine();
} }
} }
componentDidUpdate() {
const { channelName } = this.props;
if (this.state.currentSelectedValue !== channelName) {
this.setState({ currentSelectedValue: channelName });
}
}
handleCreateCancel = () => { handleCreateCancel = () => {
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 }); this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 });
}; };

View file

@ -135,9 +135,11 @@ class UriBar extends React.PureComponent {
render() { render() {
const { const {
allowEdit,
belowOverlay, belowOverlay,
navigation, navigation,
onExitSelectionMode, onExitSelectionMode,
onEditActionPressed,
onDeleteActionPressed, onDeleteActionPressed,
query, query,
selectedItemCount, selectedItemCount,
@ -175,6 +177,19 @@ class UriBar extends React.PureComponent {
</View> </View>
<View style={uriBarStyle.selectionModeActions}> <View style={uriBarStyle.selectionModeActions}>
{allowEdit && selectedItemCount === 1 && (
<TouchableOpacity
style={[uriBarStyle.actionTouchArea, uriBarStyle.leftAction]}
onPress={() => {
if (onEditActionPressed) {
onEditActionPressed();
}
}}
>
<Icon name="edit" size={20} style={uriBarStyle.actionIcon} />
</TouchableOpacity>
)}
<TouchableOpacity <TouchableOpacity
style={uriBarStyle.actionTouchArea} style={uriBarStyle.actionTouchArea}
onPress={() => { onPress={() => {

View file

@ -43,6 +43,7 @@ import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => { const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) { if (error && NativeModules.Firebase) {
console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error)); NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
} }
}; };

View file

@ -1,6 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
doFetchFileInfo, doFetchFileInfo,
doFetchClaimListMine,
doFileGet, doFileGet,
doPurchaseUri, doPurchaseUri,
doDeletePurchasedUri, doDeletePurchasedUri,
@ -18,6 +19,7 @@ import {
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectTitleForUri, makeSelectTitleForUri,
selectBalance, selectBalance,
selectMyClaimUrisWithoutChannels,
selectPurchasedUris, selectPurchasedUris,
selectFailedPurchaseUris, selectFailedPurchaseUris,
selectPurchaseUriErrorMessage, selectPurchaseUriErrorMessage,
@ -50,14 +52,15 @@ const select = (state, props) => {
contentType: makeSelectContentTypeForUri(selectProps.uri)(state), contentType: makeSelectContentTypeForUri(selectProps.uri)(state),
costInfo: makeSelectCostInfoForUri(selectProps.uri)(state), costInfo: makeSelectCostInfoForUri(selectProps.uri)(state),
metadata: makeSelectMetadataForUri(selectProps.uri)(state), metadata: makeSelectMetadataForUri(selectProps.uri)(state),
//obscureNsfw: !selectShowNsfw(state), // obscureNsfw: !selectShowNsfw(state),
//tab: makeSelectCurrentParam('tab')(state), // tab: makeSelectCurrentParam('tab')(state),
fileInfo: makeSelectFileInfoForUri(selectProps.uri)(state), fileInfo: makeSelectFileInfoForUri(selectProps.uri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps), rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps),
channelUri: makeSelectChannelForClaimUri(selectProps.uri, true)(state), channelUri: makeSelectChannelForClaimUri(selectProps.uri, true)(state),
position: makeSelectContentPositionForUri(selectProps.uri)(state), position: makeSelectContentPositionForUri(selectProps.uri)(state),
purchasedUris: selectPurchasedUris(state), purchasedUris: selectPurchasedUris(state),
failedPurchaseUris: selectFailedPurchaseUris(state), failedPurchaseUris: selectFailedPurchaseUris(state),
myClaimUris: selectMyClaimUrisWithoutChannels(state),
purchaseUriErrorMessage: selectPurchaseUriErrorMessage(state), purchaseUriErrorMessage: selectPurchaseUriErrorMessage(state),
streamingUrl: makeSelectStreamingUrlForUri(selectProps.uri)(state), streamingUrl: makeSelectStreamingUrlForUri(selectProps.uri)(state),
thumbnail: makeSelectThumbnailForUri(selectProps.uri)(state), thumbnail: makeSelectThumbnailForUri(selectProps.uri)(state),
@ -71,6 +74,7 @@ const perform = dispatch => ({
}, },
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)), fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)), fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
fileGet: (uri, saveFile) => dispatch(doFileGet(uri, saveFile)), fileGet: (uri, saveFile) => dispatch(doFileGet(uri, saveFile)),
notify: data => dispatch(doToast(data)), notify: data => dispatch(doToast(data)),
popDrawerStack: () => dispatch(doPopDrawerStack()), popDrawerStack: () => dispatch(doPopDrawerStack()),

View file

@ -231,6 +231,11 @@ class FilePage extends React.PureComponent {
} }
}; };
onEditPressed = () => {
const { claim, navigation } = this.props;
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH, params: { editMode: true, claimToEdit: claim } });
};
onDeletePressed = () => { onDeletePressed = () => {
const { claim, deleteFile, deletePurchasedUri, fileInfo, navigation } = this.props; const { claim, deleteFile, deletePurchasedUri, fileInfo, navigation } = this.props;
@ -243,11 +248,11 @@ class FilePage extends React.PureComponent {
text: 'Yes', text: 'Yes',
onPress: () => { onPress: () => {
const { uri } = navigation.state.params; const { uri } = navigation.state.params;
deleteFile(`${claim.txid}:${claim.nout}`, true); deleteFile(`${claim.txid}:${claim.nout}`, true);
deletePurchasedUri(uri); deletePurchasedUri(uri);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.deleteDownload(uri); NativeModules.UtilityModule.deleteDownload(uri);
}
this.setState({ this.setState({
downloadPressed: false, downloadPressed: false,
fileViewLogged: false, fileViewLogged: false,
@ -568,6 +573,7 @@ class FilePage extends React.PureComponent {
rewardedContentClaimIds, rewardedContentClaimIds,
isResolvingUri, isResolvingUri,
blackListedOutpoints, blackListedOutpoints,
myClaimUris,
navigation, navigation,
position, position,
purchaseUri, purchaseUri,
@ -640,19 +646,20 @@ class FilePage extends React.PureComponent {
const isPlayable = mediaType === 'video' || mediaType === 'audio'; const isPlayable = mediaType === 'video' || mediaType === 'audio';
const { height, signing_channel: signingChannel, value } = claim; const { height, signing_channel: signingChannel, value } = claim;
const channelName = signingChannel && signingChannel.name; const channelName = signingChannel && signingChannel.name;
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const canSendTip = this.state.tipAmount > 0;
const fullUri = `${claim.name}#${claim.claim_id}`;
const canEdit = myClaimUris.includes(normalizeURI(fullUri));
const showActions = const showActions =
fileInfo && (canEdit || (fileInfo && fileInfo.download_path)) &&
fileInfo.download_path &&
!this.state.fullscreenMode && !this.state.fullscreenMode &&
!this.state.showImageViewer && !this.state.showImageViewer &&
!this.state.showWebView; !this.state.showWebView;
const showFileActions = const showFileActions =
fileInfo && canEdit ||
fileInfo.download_path && (fileInfo &&
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes)); fileInfo.download_path &&
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id; (completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes)));
const canSendTip = this.state.tipAmount > 0;
const fullUri = `${claim.name}#${claim.claim_id}`;
const fullChannelUri = const fullChannelUri =
channelClaimId && channelClaimId.trim().length > 0 channelClaimId && channelClaimId.trim().length > 0
? normalizeURI(`${channelName}#${channelClaimId}`) ? normalizeURI(`${channelName}#${channelClaimId}`)
@ -823,6 +830,16 @@ class FilePage extends React.PureComponent {
<View style={filePageStyle.actions}> <View style={filePageStyle.actions}>
{showFileActions && ( {showFileActions && (
<View style={filePageStyle.fileActions}> <View style={filePageStyle.fileActions}>
{canEdit && (
<Button
style={[filePageStyle.actionButton, filePageStyle.editButton]}
theme={'light'}
icon={'edit'}
text={'Edit'}
onPress={this.onEditPressed}
/>
)}
{completed && ( {completed && (
<Button <Button
style={filePageStyle.actionButton} style={filePageStyle.actionButton}

View file

@ -18,15 +18,16 @@ import { FlatGrid } from 'react-native-super-grid';
import { import {
isNameValid, isNameValid,
buildURI, buildURI,
normalizeURI,
regexInvalidURI, regexInvalidURI,
CLAIM_VALUES, CLAIM_VALUES,
LICENSES, LICENSES,
MATURE_TAGS, MATURE_TAGS,
THUMBNAIL_STATUSES, THUMBNAIL_STATUSES,
} from 'lbry-redux'; } from 'lbry-redux';
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker';
import { RNCamera } from 'react-native-camera'; import { RNCamera } from 'react-native-camera';
import { generateCombination } from 'gfycat-style-urls'; import { generateCombination } from 'gfycat-style-urls';
import DocumentPicker from 'react-native-document-picker';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import Button from 'component/button'; import Button from 'component/button';
import ChannelSelector from 'component/channelSelector'; import ChannelSelector from 'component/channelSelector';
@ -77,6 +78,7 @@ class PublishPage extends React.PureComponent {
state = { state = {
canUseCamera: false, canUseCamera: false,
editMode: false,
titleFocused: false, titleFocused: false,
descriptionFocused: false, descriptionFocused: false,
loadingVideos: false, loadingVideos: false,
@ -114,7 +116,6 @@ class PublishPage extends React.PureComponent {
license: LICENSES.NONE, license: LICENSES.NONE,
licenseUrl: '', licenseUrl: '',
otherLicenseDescription: '', otherLicenseDescription: '',
mature: false,
name: null, name: null,
price: 0, price: 0,
uri: null, uri: null,
@ -158,7 +159,7 @@ class PublishPage extends React.PureComponent {
}; };
onComponentFocused = () => { onComponentFocused = () => {
const { pushDrawerStack, setPlayerVisible } = this.props; const { pushDrawerStack, setPlayerVisible, navigation } = this.props;
pushDrawerStack(); pushDrawerStack();
setPlayerVisible(); setPlayerVisible();
NativeModules.Firebase.setCurrentScreen('Publish'); NativeModules.Firebase.setCurrentScreen('Publish');
@ -173,6 +174,69 @@ class PublishPage extends React.PureComponent {
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos, loadingVideos: false })); NativeModules.Gallery.getVideos().then(videos => this.setState({ videos, loadingVideos: false }));
} }
); );
// Check if this is an edit action
if (navigation.state.params) {
const { editMode, claimToEdit } = navigation.state.params;
if (editMode) {
this.prepareEdit(claimToEdit);
}
}
};
prepareEdit = claim => {
let channelName;
const { amount, name, signing_channel: signingChannel, value } = claim;
const { description, fee, languages, license, license_url: licenseUrl, tags, thumbnail, title } = value;
if (signingChannel) {
channelName = signingChannel.name;
}
const thumbnailUrl = thumbnail ? thumbnail.url : null;
// Determine the license
let licenseType, otherLicenseDescription;
if (!LICENSES.CC_LICENSES.some(({ value }) => value === license)) {
if (!license || license === LICENSES.NONE || license === LICENSES.PUBLIC_DOMAIN) {
licenseType = license;
} else if (license && !licenseUrl && license !== LICENSES.NONE) {
licenseType = LICENSES.COPYRIGHT;
} else {
licenseType = LICENSES.OTHER;
}
otherLicenseDescription = license;
} else {
licenseType = license;
}
this.setState(
{
editMode: true,
currentPhase: Constants.PHASE_DETAILS,
bid: amount,
channelName,
description,
language: languages && languages.length > 0 ? languages[0] : 'en', // default to English
license: licenseType,
licenseUrl,
otherLicenseDescription,
name,
price: fee && fee.amount ? fee.amount : 0,
priceSet: fee && fee.amount > 0,
tags: tags && tags.length > 0 ? tags : [],
title,
currentThumbnailUri: thumbnailUrl,
uploadedThumbnailUri: thumbnailUrl,
},
() => {
this.handleNameChange(name);
if (channelName) {
this.handleChannelChange(channelName);
}
}
);
}; };
getNewUri(name, channel) { getNewUri(name, channel) {
@ -209,6 +273,7 @@ class PublishPage extends React.PureComponent {
handlePublishPressed = () => { handlePublishPressed = () => {
const { notify, publish, updatePublishForm } = this.props; const { notify, publish, updatePublishForm } = this.props;
const { const {
editMode,
bid, bid,
channelName, channelName,
currentMedia, currentMedia,
@ -217,7 +282,6 @@ class PublishPage extends React.PureComponent {
license, license,
licenseUrl, licenseUrl,
otherLicenseDescription, otherLicenseDescription,
mature,
name, name,
price, price,
priceSet, priceSet,
@ -237,14 +301,21 @@ class PublishPage extends React.PureComponent {
return; return;
} }
if (!currentMedia && !editMode) {
// sanity check. normally shouldn't happen
notify({ message: 'No file selected. Please select a video or take a photo before publishing.' });
return;
}
const publishParams = { const publishParams = {
filePath: currentMedia.filePath, filePath: currentMedia ? currentMedia.filePath : null,
bid: bid || 0.1, bid: bid || 0.1,
title: title || '', title: title || '',
thumbnail, thumbnail,
description: description || '', description: description || '',
language, language,
license, license,
licenseType: license,
licenseUrl, licenseUrl,
otherLicenseDescription, otherLicenseDescription,
name: name || undefined, name: name || undefined,
@ -253,12 +324,9 @@ class PublishPage extends React.PureComponent {
uri: uri || undefined, uri: uri || undefined,
channel: CLAIM_VALUES.CHANNEL_ANONYMOUS === channelName ? null : channelName, channel: CLAIM_VALUES.CHANNEL_ANONYMOUS === channelName ? null : channelName,
isStillEditing: false, isStillEditing: false,
claim: { tags: tags.map(tag => {
value: { return { name: tag };
tags, }),
release_time: Math.round(Date.now() / 1000), // set now as the release time
},
},
}; };
updatePublishForm(publishParams); updatePublishForm(publishParams);
@ -320,6 +388,7 @@ class PublishPage extends React.PureComponent {
this.setState( this.setState(
{ {
publishStarted: false, publishStarted: false,
editMode: false,
currentMedia: null, currentMedia: null,
currentThumbnailUri: null, currentThumbnailUri: null,
@ -434,16 +503,16 @@ class PublishPage extends React.PureComponent {
}; };
handleUploadPressed = () => { handleUploadPressed = () => {
DocumentPicker.show( DocumentPicker.pick({ type: [DocumentPicker.types.allFiles] })
{ .then(file => {
filetype: [DocumentPickerUtil.allFiles()], // console.log(file);
}, })
(error, res) => { .catch(error => {
if (!error) { if (!DocumentPicker.isCancel(error)) {
// console.log(res); // notify the user
// console.log(error);
} }
} });
);
}; };
getRandomFileId = () => { getRandomFileId = () => {
@ -685,9 +754,12 @@ class PublishPage extends React.PureComponent {
)} )}
</View> </View>
); );
} else if (Constants.PHASE_DETAILS === this.state.currentPhase && this.state.currentMedia) { } else if (
Constants.PHASE_DETAILS === this.state.currentPhase &&
(this.state.editMode || this.state.currentMedia)
) {
const { currentMedia, currentThumbnailUri } = this.state; const { currentMedia, currentThumbnailUri } = this.state;
if (!currentThumbnailUri) { if (!currentThumbnailUri && !this.state.editMode) {
this.updateThumbnailUriForMedia(currentMedia); this.updateThumbnailUriForMedia(currentMedia);
} }
content = ( content = (
@ -764,7 +836,7 @@ class PublishPage extends React.PureComponent {
<View style={publishStyle.card}> <View style={publishStyle.card}>
<Text style={publishStyle.cardTitle}>Channel</Text> <Text style={publishStyle.cardTitle}>Channel</Text>
<ChannelSelector onChannelChange={this.handleChannelChange} /> <ChannelSelector channelName={this.state.channelName} onChannelChange={this.handleChannelChange} />
</View> </View>
<View style={publishStyle.card}> <View style={publishStyle.card}>
@ -798,11 +870,14 @@ class PublishPage extends React.PureComponent {
<View style={publishStyle.card}> <View style={publishStyle.card}>
<Text style={publishStyle.cardTitle}>Content Address</Text> <Text style={publishStyle.cardTitle}>Content Address</Text>
<Text style={publishStyle.helpText}> <Text style={publishStyle.helpText}>
The address where people can find your content (ex. lbry://myvideo) The address where people can find your content (ex. lbry://myvideo).
{this.state.editMode &&
' You cannot change this address while editing your content. If you wish to use a new address, please republish the content.'}
</Text> </Text>
<TextInput <TextInput
placeholder={'lbry://'} placeholder={'lbry://'}
editable={!this.state.editMode}
style={publishStyle.inputText} style={publishStyle.inputText}
underlineColorAndroid={Colors.NextLbryGreen} underlineColorAndroid={Colors.NextLbryGreen}
numberOfLines={1} numberOfLines={1}
@ -902,7 +977,7 @@ class PublishPage extends React.PureComponent {
<Button <Button
style={publishStyle.publishButton} style={publishStyle.publishButton}
disabled={balance < 0.1 || !this.state.uploadedThumbnailUri} disabled={balance < 0.1 || !this.state.uploadedThumbnailUri}
text="Publish" text={this.state.editMode ? 'Save changes' : 'Publish'}
onPress={this.handlePublishPressed} onPress={this.handlePublishPressed}
/> />
</View> </View>

View file

@ -70,6 +70,16 @@ class PublishesPage extends React.PureComponent {
this.setState({ selectionMode: false, selectedUris: [], selectedClaimsMap: {} }); this.setState({ selectionMode: false, selectedUris: [], selectedClaimsMap: {} });
}; };
onEditActionPressed = () => {
const { navigation } = this.props;
const { selectedClaimsMap, selectedUris } = this.state;
// only 1 item can be edited (and edit button should be visible only if there is a single selection)
const claim = selectedClaimsMap[selectedUris[0]];
this.onExitSelectionMode();
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH, params: { editMode: true, claimToEdit: claim } });
};
onDeleteActionPressed = () => { onDeleteActionPressed = () => {
const { abandonClaim } = this.props; const { abandonClaim } = this.props;
const { selectedClaimsMap } = this.state; const { selectedClaimsMap } = this.state;
@ -103,11 +113,13 @@ class PublishesPage extends React.PureComponent {
return ( return (
<View style={publishStyle.container}> <View style={publishStyle.container}>
<UriBar <UriBar
allowEdit
navigation={navigation} navigation={navigation}
selectionMode={selectionMode} selectionMode={selectionMode}
selectedItemCount={selectedUris.length} selectedItemCount={selectedUris.length}
onExitSelectionMode={this.onExitSelectionMode}
onDeleteActionPressed={this.onDeleteActionPressed} onDeleteActionPressed={this.onDeleteActionPressed}
onEditActionPressed={this.onEditActionPressed}
onExitSelectionMode={this.onExitSelectionMode}
/> />
{fetching && ( {fetching && (
<View style={publishStyle.centered}> <View style={publishStyle.centered}>
@ -138,7 +150,6 @@ class PublishesPage extends React.PureComponent {
removeClippedSubviews removeClippedSubviews
renderItem={({ item }) => ( renderItem={({ item }) => (
<FileListItem <FileListItem
hideChannel
key={item} key={item}
uri={item} uri={item}
style={publishStyle.listItem} style={publishStyle.listItem}

View file

@ -194,6 +194,10 @@ const filePageStyle = StyleSheet.create({
}, },
fileActions: { fileActions: {
alignSelf: 'flex-end', alignSelf: 'flex-end',
flexDirection: 'row',
},
editButton: {
marginRight: 8,
}, },
socialActions: { socialActions: {
alignSelf: 'flex-start', alignSelf: 'flex-start',

View file

@ -98,6 +98,9 @@ const uriBarStyle = StyleSheet.create({
height: '100%', height: '100%',
alignItems: 'center', alignItems: 'center',
}, },
leftAction: {
marginRight: 16,
},
actionTouchArea: { actionTouchArea: {
height: '100%', height: '100%',
alignItems: 'center', alignItems: 'center',