tip button on channels, modal send tip component (#100)

* tip button on channels, modal send tip component
* add tip modal to file view
This commit is contained in:
Akinwale Ariwodola 2019-12-18 11:01:33 +01:00 committed by GitHub
parent 17b381de71
commit dd7f1e82ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 267 additions and 83 deletions

View file

@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { doSendTip, doToast, selectBalance } from 'lbry-redux';
import ModalTipView from './view';
const select = state => ({
balance: selectBalance(state),
});
const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
sendTip: (amount, claimId, isSupport, successCallback, errorCallback) =>
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
});
export default connect(
select,
perform
)(ModalTipView);

View file

@ -0,0 +1,139 @@
import React from 'react';
import { ActivityIndicator, Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { formatCredits } from 'lbry-redux';
import modalStyle from 'styles/modal';
import modalTipStyle from 'styles/modalTip';
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';
export default class ModalTipView extends React.PureComponent {
state = {
creditsInputFocused: false,
sendTipStarted: false,
tipAmount: null,
};
handleSendTip = () => {
const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sendTip } = this.props;
const { tipAmount } = this.state;
if (tipAmount > balance) {
notify({
message: 'Insufficient credits',
});
return;
}
const amount = parseInt(tipAmount, 10);
const message =
amount === 1
? __('Are you sure you want to tip %amount% credit', { amount })
: __('Are you sure you want to tip %amount% credits', { amount });
Alert.alert(
__('Send tip'),
message,
[
{ text: __('No') },
{
text: __('Yes'),
onPress: () => {
this.setState({ sendTipStarted: true }, () =>
sendTip(
tipAmount,
claim.claim_id,
false,
() => {
// success
this.setState({ tipAmount: null, sendTipStarted: false });
if (onSendTipSuccessful) onSendTipSuccessful();
},
() => {
// error
if (onSendTipFailed) onSendTipFailed();
}
)
);
},
},
],
{ cancelable: true }
);
};
render() {
const { balance, channelName, contentName, onCancelPress, onOverlayPress } = this.props;
const canSendTip = this.state.tipAmount > 0;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalStyle.container} activeOpacity={1}>
<View style={modalTipStyle.container}>
<Text style={modalTipStyle.title} numberOfLines={1}>
{channelName ? __('Send a tip to %channel%', { channel: channelName }) : __('Send a tip')}
</Text>
<View style={modalTipStyle.row}>
<View style={modalTipStyle.amountRow}>
<TextInput
editable={!this.state.sendTipStarted}
ref={ref => (this.tipAmountInput = 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.tipAmount}
selectTextOnFocus
style={modalTipStyle.tipAmountInput}
/>
<Text style={modalTipStyle.currency}>LBC</Text>
<View style={modalTipStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={modalTipStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
{this.state.sendTipStarted && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
</View>
<View style={modalTipStyle.info}>
<Text style={modalTipStyle.infoText}>
{__(
'This will appear as a tip for %content%, which will boost its ability to be discovered while active.',
{ content: contentName }
)}
</Text>
<Link
style={modalTipStyle.learnMoreLink}
text={__('Learn more.')}
href={'https://lbry.com/faq/tipping'}
/>
</View>
<View style={modalTipStyle.buttonRow}>
<Link
style={modalTipStyle.cancelTipLink}
text={__('Cancel')}
onPress={() => {
if (onCancelPress) onCancelPress();
}}
/>
<Button
text={__('Send')}
style={modalTipStyle.button}
disabled={!canSendTip || this.state.sendTipStarted}
onPress={this.handleSendTip}
/>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}

View file

@ -57,7 +57,6 @@ window.__ = __;
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

@ -23,6 +23,7 @@ import EmptyStateView from 'component/emptyStateView';
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';
import ModalPicker from 'component/modalPicker'; import ModalPicker from 'component/modalPicker';
import ModalTipView from 'component/modalTipView';
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import SubscribeNotificationButton from 'component/subscribeNotificationButton'; import SubscribeNotificationButton from 'component/subscribeNotificationButton';
@ -36,6 +37,7 @@ class ChannelPage extends React.PureComponent {
autoStyle: null, autoStyle: null,
showSortPicker: false, showSortPicker: false,
showTimePicker: false, showTimePicker: false,
showTipView: false,
orderBy: ['release_time'], // sort by new by default orderBy: ['release_time'], // sort by new by default
activeTab: Constants.CONTENT_TAB, activeTab: Constants.CONTENT_TAB,
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting channel pages by new currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting channel pages by new
@ -176,6 +178,10 @@ class ChannelPage extends React.PureComponent {
} }
}; };
onTipPressed = () => {
this.setState({ showTipView: true });
};
onSharePressed = () => { onSharePressed = () => {
const { claim } = this.props; const { claim } = this.props;
if (claim) { if (claim) {
@ -212,7 +218,7 @@ class ChannelPage extends React.PureComponent {
render() { render() {
const { channels, claim, navigation, uri, drawerStack, popDrawerStack, subCount, timeItem } = this.props; const { channels, claim, navigation, uri, drawerStack, popDrawerStack, subCount, timeItem } = this.props;
const { name, permanent_url: permanentUrl } = claim; const { name, permanent_url: permanentUrl } = claim;
const { autoStyle, currentSortByItem, showSortPicker, showTimePicker } = this.state; const { autoStyle, currentSortByItem, showSortPicker, showTimePicker, showTipView } = this.state;
const ownedChannel = channels ? channels.map(channel => channel.permanent_url).includes(permanentUrl) : false; const ownedChannel = channels ? channels.map(channel => channel.permanent_url).includes(permanentUrl) : false;
let thumbnailUrl, let thumbnailUrl,
@ -297,6 +303,12 @@ class ChannelPage extends React.PureComponent {
icon={'share-alt'} icon={'share-alt'}
onPress={this.onSharePressed} onPress={this.onSharePressed}
/> />
<Button
style={[channelPageStyle.actionButton, channelPageStyle.tipButton]}
theme={'light'}
icon={'gift'}
onPress={this.onTipPressed}
/>
{!ownedChannel && <SubscribeButton style={channelPageStyle.subscribeButton} uri={fullUri} name={name} />} {!ownedChannel && <SubscribeButton style={channelPageStyle.subscribeButton} uri={fullUri} name={name} />}
{false && !ownedChannel && ( {false && !ownedChannel && (
<SubscribeNotificationButton <SubscribeNotificationButton
@ -347,6 +359,16 @@ class ChannelPage extends React.PureComponent {
items={Constants.CLAIM_SEARCH_TIME_ITEMS} items={Constants.CLAIM_SEARCH_TIME_ITEMS}
/> />
)} )}
{showTipView && (
<ModalTipView
claim={claim}
channelName={claim.name}
contentName={title}
onCancelPress={() => this.setState({ showTipView: false })}
onOverlayPress={() => this.setState({ showTipView: false })}
onSendTipSuccessful={() => this.setState({ showTipView: false })}
/>
)}
</View> </View>
); );
} }

View file

@ -36,6 +36,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 ModalTipView from 'component/modalTipView';
import RelatedContent from 'component/relatedContent'; import RelatedContent from 'component/relatedContent';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import SubscribeNotificationButton from 'component/subscribeNotificationButton'; import SubscribeNotificationButton from 'component/subscribeNotificationButton';
@ -51,8 +52,6 @@ class FilePage extends React.PureComponent {
title: '', title: '',
}; };
tipAmountInput = null;
playerBackground = null; playerBackground = null;
scrollView = null; scrollView = null;
@ -85,7 +84,6 @@ class FilePage extends React.PureComponent {
showTipView: false, showTipView: false,
playerBgHeight: 0, playerBgHeight: 0,
playerHeight: 0, playerHeight: 0,
tipAmount: null,
uri: null, uri: null,
uriVars: null, uriVars: null,
stopDownloadConfirmed: false, stopDownloadConfirmed: false,
@ -584,39 +582,6 @@ class FilePage extends React.PureComponent {
} }
}; };
handleSendTip = () => {
const { claim, balance, navigation, notify, sendTip } = this.props;
const { uri } = navigation.state.params;
const { tipAmount } = this.state;
if (tipAmount > balance) {
notify({
message: 'Insufficient credits',
});
return;
}
const suffix = 'credit' + (parseInt(tipAmount, 10) === 1 ? '' : 's');
Alert.alert(
'Send tip',
`Are you sure you want to tip ${tipAmount} ${suffix}?`,
[
{ text: 'No' },
{
text: 'Yes',
onPress: () => {
this.setState({ sendTipStarted: true }, () =>
sendTip(tipAmount, claim.claim_id, false, () => {
this.setState({ tipAmount: null, showTipView: false, sendTipStarted: false });
})
);
},
},
],
{ cancelable: true }
);
};
renderTags = tags => { renderTags = tags => {
const { navigation } = this.props; const { navigation } = this.props;
return tags.map((tag, i) => ( return tags.map((tag, i) => (
@ -830,7 +795,6 @@ class FilePage extends React.PureComponent {
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 channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const canSendTip = this.state.tipAmount > 0;
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));
const showActions = const showActions =
@ -1169,48 +1133,6 @@ class FilePage extends React.PureComponent {
</View> </View>
</View> </View>
{this.state.showTipView && <View style={filePageStyle.divider} />}
{this.state.showTipView && (
<View style={filePageStyle.tipCard}>
<View style={filePageStyle.row}>
<View style={filePageStyle.amountRow}>
<TextInput
editable={!this.state.sendTipStarted}
ref={ref => (this.tipAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
underlineColorAndroid={Colors.NextLbryGreen}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.tipAmount}
selectTextOnFocus
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
/>
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
<View style={filePageStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={filePageStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
{this.state.sendTipStarted && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
<Link
style={[filePageStyle.link, filePageStyle.cancelTipLink]}
text={__('Cancel')}
onPress={() => this.setState({ showTipView: false })}
/>
<Button
text={__('Send a tip')}
style={[filePageStyle.button, filePageStyle.sendButton]}
disabled={!canSendTip || this.state.sendTipStarted}
onPress={this.handleSendTip}
/>
</View>
</View>
)}
{this.state.showDescription && description && description.length > 0 && ( {this.state.showDescription && description && description.length > 0 && (
<View style={filePageStyle.divider} /> <View style={filePageStyle.divider} />
)} )}
@ -1243,8 +1165,18 @@ class FilePage extends React.PureComponent {
</ScrollView> </ScrollView>
</View> </View>
)} )}
{!this.state.creditsInputFocused && {this.state.showTipView && (
!this.state.fullscreenMode && <ModalTipView
claim={claim}
channelName={channelName}
contentName={title}
onCancelPress={() => this.setState({ showTipView: false })}
onOverlayPress={() => this.setState({ showTipView: false })}
onSendTipSuccessful={() => this.setState({ showTipView: false })}
/>
)}
{!this.state.fullscreenMode &&
!this.state.showTipView &&
!this.state.showImageViewer && !this.state.showImageViewer &&
!this.state.showWebView && <FloatingWalletBalance navigation={navigation} />} !this.state.showWebView && <FloatingWalletBalance navigation={navigation} />}
</View> </View>

View file

@ -176,6 +176,9 @@ const channelPageStyle = StyleSheet.create({
marginLeft: 8, marginLeft: 8,
marginRight: 8, marginRight: 8,
}, },
tipButton: {
marginRight: 8,
},
followerCount: { followerCount: {
fontFamily: 'Inter-UI-Regular', fontFamily: 'Inter-UI-Regular',
fontSize: 12, fontSize: 12,

71
src/styles/modalTip.js Normal file
View file

@ -0,0 +1,71 @@
import { StyleSheet } from 'react-native';
import Colors from './colors';
const modalTipStyle = StyleSheet.create({
container: {
padding: 16,
},
title: {
fontFamily: 'Inter-UI-Regular',
fontSize: 24,
},
row: {
flexDirection: 'row',
flex: 1,
},
amountRow: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 24,
},
tipAmountInput: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
alignSelf: 'flex-start',
textAlign: 'right',
width: 80,
letterSpacing: 1,
},
currency: {
fontFamily: 'Inter-UI-Regular',
fontSize: 12,
marginLeft: 4,
},
buttonRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 16,
},
button: {
backgroundColor: Colors.LbryGreen,
},
cancelTipLink: {
color: Colors.Grey,
},
balance: {
alignItems: 'center',
flexDirection: 'row',
marginLeft: 24,
},
balanceText: {
fontFamily: 'Inter-UI-SemiBold',
fontSize: 14,
marginLeft: 4,
},
info: {
marginTop: 4,
},
infoText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
color: Colors.DescriptionGrey,
},
learnMoreLink: {
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
color: Colors.LbryGreen,
},
});
export default modalTipStyle;