Unverified Commit a5953dce authored by Akinwale Ariwodola's avatar Akinwale Ariwodola Committed by GitHub
Browse files

Repost creation (#133)

* create reposts
* display reposts properly on publishes page
* show/hide advanced link
* fix edit mode on owned publishes
parent d33ed55b
Pipeline #1792 passed with stage
Subproject commit 56c375f344911bffe64c0564dd37d2bb8b7761f1 Subproject commit 4d1f142f9cf4f221a2110b7d7aa77b9f42731253
...@@ -99,6 +99,7 @@ class FileListItem extends React.PureComponent { ...@@ -99,6 +99,7 @@ class FileListItem extends React.PureComponent {
isRewardContent, isRewardContent,
channelClaimId, channelClaimId,
fullChannelUri, fullChannelUri,
repostUrl,
repostChannel, repostChannel,
repostChannelUrl, repostChannelUrl,
shortChannelUri, shortChannelUri,
...@@ -114,12 +115,13 @@ class FileListItem extends React.PureComponent { ...@@ -114,12 +115,13 @@ class FileListItem extends React.PureComponent {
channelClaimId = signingChannel ? signingChannel.claim_id : null; channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel; fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
shortChannelUri = signingChannel ? signingChannel.short_url : null; shortChannelUri = signingChannel ? signingChannel.short_url : null;
repostUrl = claim.repost_url;
repostChannelUrl = claim.repost_channel_url; repostChannelUrl = claim.repost_channel_url;
if (repostChannelUrl) { if (repostUrl) {
const { claimName: repostChannelName } = parseURI(repostChannelUrl); const { claimName: repostName, channelName } = parseURI(repostUrl);
repostChannel = repostChannelName; repostChannel = channelName;
} }
isRepost = !!repostChannelUrl; isRepost = !!repostUrl;
if (blackListedOutpoints || filteredOutpoints) { if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints const outpointsToHide = !blackListedOutpoints
...@@ -148,8 +150,17 @@ class FileListItem extends React.PureComponent { ...@@ -148,8 +150,17 @@ class FileListItem extends React.PureComponent {
<Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} /> <Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} />
<Text style={fileListStyle.repostChannelName}> <Text style={fileListStyle.repostChannelName}>
<Link <Link
text={repostChannel} text={`@${repostChannel}`}
onPress={() => navigateToUri(navigation, normalizeURI(repostChannelUrl), null, false, null, false)} onPress={() =>
navigateToUri(
navigation,
normalizeURI(repostChannelUrl || `@${repostChannel}`),
null,
false,
null,
false,
)
}
/>{' '} />{' '}
reposted reposted
</Text> </Text>
......
import { connect } from 'react-redux';
import {
doClearRepostError,
doFetchChannelListMine,
doRepost,
doToast,
selectBalance,
selectMyChannelClaims,
selectRepostError,
selectRepostLoading,
} from 'lbry-redux';
import ModalRepostView from './view';
const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state),
reposting: selectRepostLoading(state),
error: selectRepostError(state),
});
const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
notify: data => dispatch(doToast(data)),
repost: options => dispatch(doRepost(options)),
clearError: () => dispatch(doClearRepostError()),
});
export default connect(select, perform)(ModalRepostView);
import React from 'react';
import { ActivityIndicator, Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { formatCredits, creditsToString } from 'lbry-redux';
import modalStyle from 'styles/modal';
import modalRepostStyle from 'styles/modalRepost';
import ChannelSelector from 'component/channelSelector';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import { logPublish } from 'utils/helper';
export default class ModalRepostView extends React.PureComponent {
depositAmountInput;
state = {
channelName: null,
creditsInputFocused: false,
depositAmount: '0.1',
repostName: null,
repostStarted: false,
showAdvanced: false,
};
componentDidMount() {
const { claim, fetchChannelListMine } = this.props;
const { name } = claim;
fetchChannelListMine();
this.setState({ repostName: name });
this.checkChannelSelection(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkChannelSelection(nextProps);
const { notify } = this.props;
const { reposting, error } = nextProps;
if (this.state.repostStarted && !reposting && error) {
this.setState({ repostStarted: false });
notify({ message: error, isError: true });
}
}
checkChannelSelection = props => {
const { channels = [] } = props;
if (!this.state.channelName && channels && channels.length > 0) {
const firstChannel = channels[0];
this.setState({ channelName: firstChannel.name });
}
};
handleChannelChange = channelName => {
const { channels = [] } = this.props;
if (channels && channels.length > 0) {
const filtered = channels.filter(c => c.name === channelName);
if (filtered.length > 0) {
const channel = filtered[0];
this.setState({ channelName });
}
}
};
handleRepost = () => {
const { claim, balance, notify, repost, onRepostSuccessful, channels = [], clearError } = this.props;
const { depositAmount, repostName, channelName } = this.state;
if (parseInt(depositAmount, 10) > balance) {
notify({
message: 'Insufficient credits',
isError: true,
});
return;
}
clearError();
const channel = channels.filter(ch => ch.name === channelName)[0];
this.setState({ repostStarted: true }, () => {
repost({
name: repostName,
bid: creditsToString(depositAmount),
channel_id: channel.claim_id,
claim_id: claim.claim_id,
}).then(repostClaim => {
logPublish(repostClaim);
this.setState({ repostStarted: false });
notify({ message: __('The content was successfully reposted!') });
if (onRepostSuccessful) onRepostSuccessful();
});
});
};
render() {
const { balance, channels, reposting, title, onCancelPress, onOverlayPress } = this.props;
const canRepost = !!this.state.channelName && !!this.state.repostName;
const channelsLoaded = channels && channels.length > 0;
const processing = this.state.repostStarted || reposting || !channelsLoaded;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalStyle.container} activeOpacity={1}>
<View
style={modalRepostStyle.container}
onLayout={() => {
if (this.tipAmountInput) {
this.tipAmountInput.focus();
}
}}
>
<Text style={modalRepostStyle.title} numberOfLines={1}>
{__('Repost %title%', { title })}
</Text>
<Text style={modalRepostStyle.infoText}>
{__('Repost your favorite content to help more people discover them!')}
</Text>
<Text style={modalRepostStyle.label}>{__('Channel to post on')}</Text>
<ChannelSelector
showAnonymous={false}
channelName={this.state.channelName}
onChannelChange={this.handleChannelChange}
/>
{this.state.showAdvanced && (
<View>
<Text style={modalRepostStyle.label}>{__('Name')}</Text>
<View style={modalRepostStyle.nameRow}>
<TextInput
editable={false}
value={this.state.channelName ? `lbry://${this.state.channelName}/` : ''}
style={modalRepostStyle.input}
/>
<TextInput
editable={canRepost}
value={this.state.repostName}
underlineColorAndroid={Colors.NextLbryGreen}
selectTextOnFocus
onChangeText={value => this.setState({ repostName: value })}
style={modalRepostStyle.input}
/>
</View>
<Text style={modalRepostStyle.label}>{__('Deposit')}</Text>
<View style={modalRepostStyle.row}>
<View style={modalRepostStyle.amountRow}>
<TextInput
editable={!this.state.repostStarted}
ref={ref => (this.depositAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
underlineColorAndroid={Colors.NextLbryGreen}
keyboardType={'numeric'}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
placeholder={'0'}
value={this.state.depositAmount}
selectTextOnFocus
style={modalRepostStyle.depositAmountInput}
/>
<Text style={modalRepostStyle.currency}>LBC</Text>
<View style={modalRepostStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={modalRepostStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
</View>
</View>
)}
<View style={modalRepostStyle.buttonRow}>
{!processing && (
<Link
style={modalRepostStyle.cancelLink}
text={__('Cancel')}
onPress={() => {
if (onCancelPress) onCancelPress();
}}
/>
)}
{processing && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
<View style={modalRepostStyle.rightButtonRow}>
<Link
style={modalRepostStyle.advancedLink}
text={this.state.showAdvanced ? __('Hide advanced') : __('Show advanced')}
onPress={() => {
this.setState({ showAdvanced: !this.state.showAdvanced });
}}
/>
<Button
text={__('Repost')}
style={modalRepostStyle.button}
disabled={!canRepost || this.state.repostStarted || reposting}
onPress={this.handleRepost}
/>
</View>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}
...@@ -5,7 +5,6 @@ import modalStyle from 'styles/modal'; ...@@ -5,7 +5,6 @@ import modalStyle from 'styles/modal';
import modalTipStyle from 'styles/modalTip'; import modalTipStyle from 'styles/modalTip';
import Button from 'component/button'; import Button from 'component/button';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link'; import Link from 'component/link';
...@@ -25,6 +24,7 @@ export default class ModalTipView extends React.PureComponent { ...@@ -25,6 +24,7 @@ export default class ModalTipView extends React.PureComponent {
if (tipAmount > balance) { if (tipAmount > balance) {
notify({ notify({
message: 'Insufficient credits', message: 'Insufficient credits',
isError: true,
}); });
return; return;
} }
...@@ -56,13 +56,13 @@ export default class ModalTipView extends React.PureComponent { ...@@ -56,13 +56,13 @@ export default class ModalTipView extends React.PureComponent {
() => { () => {
// error // error
if (onSendTipFailed) onSendTipFailed(); if (onSendTipFailed) onSendTipFailed();
} },
) ),
); );
}, },
}, },
], ],
{ cancelable: true } { cancelable: true },
); );
}; };
...@@ -115,7 +115,7 @@ export default class ModalTipView extends React.PureComponent { ...@@ -115,7 +115,7 @@ export default class ModalTipView extends React.PureComponent {
<Text style={modalTipStyle.infoText}> <Text style={modalTipStyle.infoText}>
{__( {__(
'This will appear as a tip for %content%, which will boost its ability to be discovered while active.', 'This will appear as a tip for %content%, which will boost its ability to be discovered while active.',
{ content: contentName } { content: contentName },
)}{' '} )}{' '}
<Link <Link
style={modalTipStyle.learnMoreLink} style={modalTipStyle.learnMoreLink}
......
...@@ -149,7 +149,7 @@ const Constants = { ...@@ -149,7 +149,7 @@ const Constants = {
TRUE_STRING: 'true', TRUE_STRING: 'true',
MINIMUM_TRANSACTION_BALANCE: 0.1, MINIMUM_TRANSACTION_BALANCE: 0.01,
SHARE_BASE_URL: 'https://open.lbry.com', SHARE_BASE_URL: 'https://open.lbry.com',
}; };
......
...@@ -37,6 +37,7 @@ import FilePrice from 'component/filePrice'; ...@@ -37,6 +37,7 @@ import FilePrice from 'component/filePrice';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
import Link from 'component/link'; import Link from 'component/link';
import MediaPlayer from 'component/mediaPlayer'; import MediaPlayer from 'component/mediaPlayer';
import ModalRepostView from 'component/modalRepostView';
import ModalTipView from 'component/modalTipView'; import ModalTipView from 'component/modalTipView';
import ProgressCircle from 'react-native-progress-circle'; import ProgressCircle from 'react-native-progress-circle';
import RelatedContent from 'component/relatedContent'; import RelatedContent from 'component/relatedContent';
...@@ -94,6 +95,7 @@ class FilePage extends React.PureComponent { ...@@ -94,6 +95,7 @@ class FilePage extends React.PureComponent {
showImageViewer: false, showImageViewer: false,
showWebView: false, showWebView: false,
showTipView: false, showTipView: false,
showRepostView: false,
playbackStarted: false, playbackStarted: false,
playerBgHeight: 0, playerBgHeight: 0,
playerHeight: 0, playerHeight: 0,
...@@ -265,6 +267,7 @@ class FilePage extends React.PureComponent { ...@@ -265,6 +267,7 @@ class FilePage extends React.PureComponent {
'playerBgHeighht', 'playerBgHeighht',
'playerHeight', 'playerHeight',
'relatedY', 'relatedY',
'showRepostView',
'showTipView', 'showTipView',
'showImageViewer', 'showImageViewer',
'showWebView', 'showWebView',
...@@ -1127,7 +1130,8 @@ class FilePage extends React.PureComponent { ...@@ -1127,7 +1130,8 @@ class FilePage extends React.PureComponent {
const channelName = signingChannel && signingChannel.name; const channelName = signingChannel && signingChannel.name;
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id; const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const fullUri = `${claim.name}#${claim.claim_id}`; const fullUri = `${claim.name}#${claim.claim_id}`;
const canEdit = myClaimUris.includes(normalizeURI(fullUri)); const canEdit =
myClaimUris.includes(normalizeURI(fullUri)) || myClaimUris.includes(normalizeURI(claim.canonical_url));
const showActions = const showActions =
(canEdit || (fileInfo && fileInfo.download_path)) && (canEdit || (fileInfo && fileInfo.download_path)) &&
!this.state.fullscreenMode && !this.state.fullscreenMode &&
...@@ -1348,6 +1352,14 @@ class FilePage extends React.PureComponent { ...@@ -1348,6 +1352,14 @@ class FilePage extends React.PureComponent {
<Text style={filePageStyle.largeButtonText}>{__('Share')}</Text> <Text style={filePageStyle.largeButtonText}>{__('Share')}</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity
style={filePageStyle.largeButton}
onPress={() => this.setState({ showRepostView: true })}
>
<Icon name={'retweet'} size={16} style={filePageStyle.largeButtonIcon} />
<Text style={filePageStyle.largeButtonText}>{__('Repost')}</Text>
</TouchableOpacity>
<TouchableOpacity <TouchableOpacity
style={filePageStyle.largeButton} style={filePageStyle.largeButton}
onPress={() => this.setState({ showTipView: true })} onPress={() => this.setState({ showTipView: true })}
...@@ -1522,7 +1534,17 @@ class FilePage extends React.PureComponent { ...@@ -1522,7 +1534,17 @@ class FilePage extends React.PureComponent {
onSendTipSuccessful={() => this.setState({ showTipView: false })} onSendTipSuccessful={() => this.setState({ showTipView: false })}
/> />
)} )}
{this.state.showRepostView && (
<ModalRepostView
claim={claim}
title={title}
onCancelPress={() => this.setState({ showRepostView: false })}
onOverlayPress={() => this.setState({ showRepostView: false })}
onRepostSuccessful={() => this.setState({ showRepostView: false })}
/>
)}
{!this.state.fullscreenMode && {!this.state.fullscreenMode &&
!this.state.showRepostView &&
!this.state.showTipView && !this.state.showTipView &&
!this.state.showImageViewer && !this.state.showImageViewer &&
!this.state.showWebView && <FloatingWalletBalance navigation={navigation} />} !this.state.showWebView && <FloatingWalletBalance navigation={navigation} />}
......
...@@ -322,6 +322,7 @@ class SplashScreen extends React.PureComponent { ...@@ -322,6 +322,7 @@ class SplashScreen extends React.PureComponent {
componentDidMount() { componentDidMount() {
NativeModules.Firebase.track('app_launch', null); NativeModules.Firebase.track('app_launch', null);
NativeModules.Firebase.setCurrentScreen('Splash'); NativeModules.Firebase.setCurrentScreen('Splash');
NativeModules.UtilityModule.checkSdkReady();
const { navigation } = this.props; const { navigation } = this.props;
const { resetUrl } = navigation.state.params; const { resetUrl } = navigation.state.params;
......
import { StyleSheet } from 'react-native';
import Colors from './colors';
const modalRepostStyle = StyleSheet.create({
container: {
padding: 16,
},
title: {
fontFamily: 'Inter-Regular',
fontSize: 24,
marginBottom: 8,
},
row: {
flexDirection: 'row',
flex: 1,
},
amountRow: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 24,
},
depositAmountInput: {
fontFamily: 'Inter-Regular',
fontSize: 14,
alignSelf: 'flex-start',
textAlign: 'right',
width: 80,
letterSpacing: 1,
},
currency: {
fontFamily: 'Inter-Regular',
fontSize: 12,
marginLeft: 4,
},
buttonRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 16,
},
button: {
backgroundColor: Colors.LbryGreen,
},
cancelLink: {
color: Colors.Grey,
},
advancedLink: {
color: Colors.Grey,
marginRight: 16,
},
balance: {
alignItems: 'center',
flexDirection: 'row',
marginLeft: 24,
},
balanceText: {
fontFamily: 'Inter-SemiBold',
fontSize: 14,
marginLeft: 4,
},
info: {
marginTop: 4,
},
infoText: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.DescriptionGrey,
},
learnMoreLink: {
fontFamily: 'Inter-Regular',
fontSize: 14,
color: Colors.LbryGreen,
},
label: {
fontFamily: 'Inter-Regular',
fontSize: 14,
marginTop: 8,
},
nameRow: {
flexDirection: 'row',
alignItems: 'center',
},
rightButtonRow: {
flexDirection: 'row',
alignItems: 'center',
},
input: {
fontFamily: 'Inter-Regular',
fontSize: 14,
},
});
export default modalRepostStyle;
...@@ -411,6 +411,7 @@ const publishStyle = StyleSheet.create({ ...@@ -411,6 +411,7 @@ const publishStyle = StyleSheet.create({
marginTop: 60, marginTop: 60,
}, },
publishesScrollPadding: { publishesScrollPadding: {
paddingTop: 16,
paddingBottom: 16, paddingBottom: 16,
}, },
listItem: { listItem: {
...@@ -418,8 +419,9 @@ const publishStyle = StyleSheet.create({ ...@@ -418,8 +419,9 @@ const publishStyle = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
marginTop: 8, marginTop: 8,
marginLeft: 8, marginLeft: 16,
marginRight: 8, marginRight: 16,