Publishing #577

Merged
akinwale merged 15 commits from publishing into master 2019-07-09 02:43:31 +02:00
152 changed files with 4771 additions and 2637 deletions
Showing only changes of commit b58f2db030 - Show all commits

36
app/.eslintrc.json Normal file
View file

@ -0,0 +1,36 @@
{
"parser": "babel-eslint",
"extends": ["standard", "standard-jsx", "plugin:flowtype/recommended"],
"plugins": ["flowtype", "import"],
"env": {
"browser": true,
"node": true
},
"globals": {
"__": true
},
"rules": {
"no-multi-spaces": 0,
"new-cap": 0,
"prefer-promise-reject-errors": 0,
"no-unused-vars": 0,
"standard/object-curly-even-spacing": 0,
"handle-callback-err": 0,
"one-var": 0,
"object-curly-spacing": 0,
"no-redeclare": 0,
"no-return-await": 0,
"standard/no-callback-literal": 0,
"comma-dangle": ["error", "always-multiline"],
"space-before-function-paren": ["error", "never"],
"jsx-quotes": ["error", "prefer-double"],
"no-use-before-define": 0,
"semi": [
"error",
"always",
{
"omitLastInOneLineBlock": true
}
]
}
}

6
app/.lintstagedrc.json Normal file
View file

@ -0,0 +1,6 @@
{
"linters": {
"src/**/*.{js,json}": ["prettier --write", "git add"],
"src/**/*.js": ["eslint --fix", "git add"]
}
}

5
app/.prettierrc.json Normal file
View file

@ -0,0 +1,5 @@
{
"trailingComma": "es5",
"printWidth": 120,
"singleQuote": true
}

1580
app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,9 @@
"version": "0.0.1",
"private": "true",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
"start": "node node_modules/react-native/local-cli/cli.js start",
"format": "prettier 'src/**/*.{js,json}' --write",
"precommit": "lint-staged"
},
"dependencies": {
"base-64": "^0.1.0",
@ -43,6 +45,17 @@
"babel-preset-react-native": "5.0.2",
"babel-preset-stage-2": "^6.18.0",
"babel-plugin-module-resolver": "^3.1.1",
"flow-babel-webpack-plugin": "^1.1.1"
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-config-standard-jsx": "^6.0.2",
"eslint-plugin-flowtype": "^2.46.1",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-standard": "^4.0.0",
"flow-babel-webpack-plugin": "^1.1.1",
"lint-staged": "^7.0.4",
"prettier": "^1.11.1"
}
}

View file

@ -15,25 +15,14 @@ import SubscriptionsPage from 'page/subscriptions';
import TransactionHistoryPage from 'page/transactionHistory';
import VerificationScreen from 'page/verification';
import WalletPage from 'page/wallet';
import {
createDrawerNavigator,
createStackNavigator,
NavigationActions
} from 'react-navigation';
import { createDrawerNavigator, createStackNavigator, NavigationActions } from 'react-navigation';
import {
createReduxContainer,
createReactNavigationReduxMiddleware,
createNavigationReducer
createNavigationReducer,
} from 'react-navigation-redux-helpers';
import { connect } from 'react-redux';
import {
AppState,
BackHandler,
Linking,
NativeModules,
TextInput,
ToastAndroid
} from 'react-native';
import { AppState, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
import {
@ -44,7 +33,7 @@ import {
selectEmailToVerify,
selectEmailVerifyIsPending,
selectEmailVerifyErrorMessage,
selectUser
selectUser,
} from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { decode as atob } from 'base-64';
@ -58,37 +47,43 @@ import discoverStyle from 'styles/discover';
import searchStyle from 'styles/search';
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
const menuNavigationButton = (navigation) => <NavigationButton
name="bars"
size={24}
style={discoverStyle.drawerMenuButton}
iconStyle={discoverStyle.drawerHamburger}
onPress={() => navigation.openDrawer() } />
const menuNavigationButton = navigation => (
<NavigationButton
name="bars"
size={24}
style={discoverStyle.drawerMenuButton}
iconStyle={discoverStyle.drawerHamburger}
onPress={() => navigation.openDrawer()}
/>
);
const discoverStack = createStackNavigator({
Discover: {
screen: DiscoverPage,
navigationOptions: ({ navigation }) => ({
title: 'Explore',
header: null
}),
const discoverStack = createStackNavigator(
{
Discover: {
screen: DiscoverPage,
navigationOptions: ({ navigation }) => ({
title: 'Explore',
header: null,
}),
},
File: {
screen: FilePage,
navigationOptions: ({ navigation }) => ({
header: null,
}),
},
Search: {
screen: SearchPage,
navigationOptions: ({ navigation }) => ({
header: null,
}),
},
},
File: {
screen: FilePage,
navigationOptions: ({ navigation }) => ({
header: null
})
},
Search: {
screen: SearchPage,
navigationOptions: ({ navigation }) => ({
header: null
})
{
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
}
}, {
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
});
);
discoverStack.navigationOptions = ({ navigation }) => {
let drawerLockMode = 'unlocked';
@ -97,105 +92,137 @@ discoverStack.navigationOptions = ({ navigation }) => {
}*/
return {
drawerLockMode
drawerLockMode,
};
};
const walletStack = createStackNavigator({
Wallet: {
screen: WalletPage,
navigationOptions: ({ navigation }) => ({
title: 'Wallet',
header: null
})
const walletStack = createStackNavigator(
{
Wallet: {
screen: WalletPage,
navigationOptions: ({ navigation }) => ({
title: 'Wallet',
header: null,
}),
},
TransactionHistory: {
screen: TransactionHistoryPage,
navigationOptions: {
title: 'Transaction History',
header: null,
},
},
},
TransactionHistory: {
screen: TransactionHistoryPage,
navigationOptions: {
title: 'Transaction History',
header: null
}
{
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
}
}, {
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
});
);
const drawer = createDrawerNavigator({
DiscoverStack: { screen: discoverStack, navigationOptions: {
title: 'Explore', drawerIcon: ({ tintColor }) => <Icon name="home" size={20} style={{ color: tintColor }} />
}},
TrendingStack: { screen: TrendingPage, navigationOptions: {
title: 'Trending', drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />
}},
MySubscriptionsStack: { screen: SubscriptionsPage, navigationOptions: {
title: 'Subscriptions', drawerIcon: ({ tintColor }) => <Icon name="heart" solid={true} size={20} style={{ color: tintColor }} />
}},
WalletStack: { screen: walletStack, navigationOptions: {
title: 'Wallet', drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />
}},
Rewards: { screen: RewardsPage, navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="award" size={20} style={{ color: tintColor }} />
}},
Publish: { screen: PublishPage, navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="upload" size={20} style={{ color: tintColor }} />
}},
MyLBRYStack: { screen: DownloadsPage, navigationOptions: {
title: 'Library', drawerIcon: ({ tintColor }) => <Icon name="download" size={20} style={{ color: tintColor }} />
}},
Settings: { screen: SettingsPage, navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />
}},
About: { screen: AboutPage, navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />
}}
}, {
drawerWidth: 300,
headerMode: 'none',
contentComponent: DrawerContent,
contentOptions: {
activeTintColor: Colors.LbryGreen,
labelStyle: discoverStyle.menuText
const drawer = createDrawerNavigator(
{
DiscoverStack: {
screen: discoverStack,
navigationOptions: {
title: 'Explore',
drawerIcon: ({ tintColor }) => <Icon name="home" size={20} style={{ color: tintColor }} />,
},
},
TrendingStack: {
screen: TrendingPage,
navigationOptions: {
title: 'Trending',
drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />,
},
},
MySubscriptionsStack: {
screen: SubscriptionsPage,
navigationOptions: {
title: 'Subscriptions',
drawerIcon: ({ tintColor }) => <Icon name="heart" solid={true} size={20} style={{ color: tintColor }} />,
},
},
WalletStack: {
screen: walletStack,
navigationOptions: {
title: 'Wallet',
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
},
},
Rewards: {
screen: RewardsPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="award" size={20} style={{ color: tintColor }} />,
},
},
MyLBRYStack: {
screen: DownloadsPage,
navigationOptions: {
title: 'Library',
drawerIcon: ({ tintColor }) => <Icon name="download" size={20} style={{ color: tintColor }} />,
},
},
Settings: {
screen: SettingsPage,
navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />,
},
},
About: {
screen: AboutPage,
navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />,
},
},
},
{
drawerWidth: 300,
headerMode: 'none',
contentComponent: DrawerContent,
contentOptions: {
activeTintColor: Colors.LbryGreen,
labelStyle: discoverStyle.menuText,
},
}
});
);
const mainStackNavigator = new createStackNavigator({
FirstRun: {
screen: FirstRunScreen,
navigationOptions: {
drawerLockMode: 'locked-closed'
}
const mainStackNavigator = new createStackNavigator(
{
FirstRun: {
screen: FirstRunScreen,
navigationOptions: {
drawerLockMode: 'locked-closed',
},
},
Splash: {
screen: SplashScreen,
navigationOptions: {
drawerLockMode: 'locked-closed',
},
},
Main: {
screen: drawer,
},
Verification: {
screen: VerificationScreen,
navigationOptions: {
drawerLockMode: 'locked-closed',
},
},
},
Splash: {
screen: SplashScreen,
navigationOptions: {
drawerLockMode: 'locked-closed'
}
},
Main: {
screen: drawer
},
Verification: {
screen: VerificationScreen,
navigationOptions: {
drawerLockMode: 'locked-closed'
}
{
headerMode: 'none',
}
}, {
headerMode: 'none'
});
);
export const AppNavigator = mainStackNavigator;
export const navigatorReducer = createNavigationReducer(AppNavigator);
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
state => state.nav,
);
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(state => state.nav);
const App = createReduxContainer(mainStackNavigator);
const appMapStateToProps = (state) => ({
const appMapStateToProps = state => ({
state: state.nav,
});
const ReduxAppNavigator = connect(appMapStateToProps)(App);
@ -208,29 +235,34 @@ class AppWithNavigationState extends React.Component {
this.emailVerifyCheckInterval = null;
this.state = {
emailVerifyDone: false,
verifyPending: false
verifyPending: false,
};
}
componentWillMount() {
AppState.addEventListener('change', this._handleAppStateChange);
BackHandler.addEventListener('hardwareBackPress', function() {
const { dispatch, nav, drawerStack } = this.props;
// There should be a better way to check this
if (nav.routes.length > 0) {
if (nav.routes[0].routeName === 'Main') {
const mainRoute = nav.routes[0];
if (mainRoute.index > 0 ||
BackHandler.addEventListener(
'hardwareBackPress',
function() {
const { dispatch, nav, drawerStack } = this.props;
// There should be a better way to check this
if (nav.routes.length > 0) {
if (nav.routes[0].routeName === 'Main') {
const mainRoute = nav.routes[0];
if (
mainRoute.index > 0 ||
mainRoute.routes[0].index > 0 /* Discover stack index */ ||
mainRoute.routes[4].index > 0 /* Wallet stack index */ ||
mainRoute.index >= 5 /* Settings and About screens */) {
dispatchNavigateBack(dispatch, nav, drawerStack);
return true;
mainRoute.index >= 5 /* Settings and About screens */
) {
dispatchNavigateBack(dispatch, nav, drawerStack);
return true;
}
}
}
}
return false;
}.bind(this));
return false;
}.bind(this)
);
}
componentDidMount() {
@ -241,12 +273,12 @@ class AppWithNavigationState extends React.Component {
checkEmailVerification = () => {
const { dispatch } = this.props;
AsyncStorage.getItem(Constants.KEY_EMAIL_VERIFY_PENDING).then(pending => {
this.setState({ verifyPending: ('true' === pending) });
this.setState({ verifyPending: 'true' === pending });
if ('true' === pending) {
dispatch(doUserCheckEmailVerified());
}
});
}
};
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
@ -274,13 +306,7 @@ class AppWithNavigationState extends React.Component {
componentWillUpdate(nextProps) {
const { dispatch } = this.props;
const {
toast,
emailToVerify,
emailVerifyPending,
emailVerifyErrorMessage,
user
} = nextProps;
const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps;
if (toast) {
const { message } = toast;
@ -297,15 +323,13 @@ class AppWithNavigationState extends React.Component {
dispatch(doDismissToast());
}
if (user &&
!emailVerifyPending &&
!this.state.emailVerifyDone &&
(emailToVerify || emailVerifyErrorMessage)) {
if (user && !emailVerifyPending && !this.state.emailVerifyDone && (emailToVerify || emailVerifyErrorMessage)) {
AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => {
if ('true' === shouldVerify) {
this.setState({ emailVerifyDone: true });
const message = emailVerifyErrorMessage ?
String(emailVerifyErrorMessage) : 'Your email address was successfully verified.';
const message = emailVerifyErrorMessage
? String(emailVerifyErrorMessage)
: 'Your email address was successfully verified.';
if (!emailVerifyErrorMessage) {
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
}
@ -317,7 +341,7 @@ class AppWithNavigationState extends React.Component {
}
}
_handleAppStateChange = (nextAppState) => {
_handleAppStateChange = nextAppState => {
const { backgroundPlayEnabled, dispatch } = this.props;
// Check if the app was suspended
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
@ -341,9 +365,9 @@ class AppWithNavigationState extends React.Component {
NativeModules.BackgroundMedia.hidePlaybackNotification();
}
}
}
};
_handleUrl = (evt) => {
_handleUrl = evt => {
const { dispatch, nav } = this.props;
if (evt.url) {
if (evt.url.startsWith('lbry://?verify=')) {
@ -365,15 +389,17 @@ class AppWithNavigationState extends React.Component {
dispatch(doToast({ message }));
}
} else {
dispatch(doToast({
message: 'Invalid Verification URI',
}));
dispatch(
doToast({
message: 'Invalid Verification URI',
})
);
}
} else {
dispatchNavigateToUri(dispatch, nav, evt.url);
}
}
}
};
render() {
return <ReduxAppNavigator />;
@ -390,7 +416,7 @@ const mapStateToProps = state => ({
emailVerifyPending: selectEmailVerifyIsPending(state),
emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
user: selectUser(state)
user: selectUser(state),
});
export default connect(mapStateToProps)(AppWithNavigationState);

View file

@ -2,6 +2,9 @@ import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import Address from './view';
export default connect(null, {
doToast,
})(Address);
export default connect(
null,
{
doToast,
}
)(Address);

View file

@ -15,13 +15,19 @@ export default class Address extends React.PureComponent<Props> {
return (
<View style={[walletStyle.row, style]}>
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>{address || ''}</Text>
<Button icon={'clipboard'} style={walletStyle.button} onPress={() => {
Clipboard.setString(address);
doToast({
message: 'Address copied',
});
}} />
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>
{address || ''}
</Text>
<Button
icon={'clipboard'}
style={walletStyle.button}
onPress={() => {
Clipboard.setString(address);
doToast({
message: 'Address copied',
});
}}
/>
</View>
);
}

View file

@ -1,4 +1,7 @@
import { connect } from 'react-redux';
import Button from './view';
export default connect(null, null)(Button);
export default connect(
null,
null
)(Button);

View file

@ -6,17 +6,7 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
export default class Button extends React.PureComponent {
render() {
const {
disabled,
style,
text,
icon,
iconColor,
solid,
theme,
onPress,
onLayout
} = this.props;
const { disabled, style, text, icon, iconColor, solid, theme, onPress, onLayout } = this.props;
let styles = [buttonStyle.button, buttonStyle.row];
if (style) {
@ -43,16 +33,25 @@ export default class Button extends React.PureComponent {
textStyles.push(buttonStyle.textLight);
}
let renderIcon = (<Icon name={icon} size={18} color={iconColor ? iconColor : ('light' === theme ? Colors.DarkGrey : Colors.White)} />);
let renderIcon = (
<Icon name={icon} size={18} color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White} />
);
if (solid) {
renderIcon = (<Icon name={icon} size={18} color={iconColor ? iconColor : ('light' === theme ? Colors.DarkGrey : Colors.White)} solid />);
renderIcon = (
<Icon
name={icon}
size={18}
color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White}
solid
/>
);
}
return (
<TouchableOpacity disabled={disabled} style={styles} onPress={onPress} onLayout={onLayout}>
{icon && renderIcon}
{text && (text.trim().length > 0) && <Text style={textStyles}>{text}</Text>}
{text && text.trim().length > 0 && <Text style={textStyles}>{text}</Text>}
</TouchableOpacity>
);
}
};
}

View file

@ -1,4 +1,7 @@
import { connect } from 'react-redux';
import CategoryList from './view';
export default connect(null, null)(CategoryList);
export default connect(
null,
null
)(CategoryList);

View file

@ -16,7 +16,7 @@ class CategoryList extends React.PureComponent {
initialNumToRender={3}
maxToRenderPerBatch={3}
removeClippedSubviews={true}
renderItem={ ({item}) => (
renderItem={({ item }) => (
<FileItem
style={discoverStyle.fileItem}
mediaStyle={discoverStyle.fileItemMedia}
@ -24,9 +24,9 @@ class CategoryList extends React.PureComponent {
uri={normalizeURI(item)}
navigation={navigation}
showDetails={true}
compactView={false} />
)
}
compactView={false}
/>
)}
horizontal={true}
showsHorizontalScrollIndicator={false}
data={categoryMap[category]}

View file

@ -5,7 +5,7 @@ import {
doClaimRewardClearError,
makeSelectClaimRewardError,
makeSelectIsRewardClaimPending,
rewards as REWARD_TYPES
rewards as REWARD_TYPES,
} from 'lbryinc';
import CustomRewardCard from './view';
@ -20,7 +20,10 @@ const perform = dispatch => ({
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
clearError: reward => dispatch(doClaimRewardClearError(reward)),
notify: data => dispatch(doToast(data)),
submitRewardCode: code => dispatch(doClaimRewardType(REWARD_TYPES.TYPE_REWARD_CODE, { params: { code } }))
submitRewardCode: code => dispatch(doClaimRewardType(REWARD_TYPES.TYPE_REWARD_CODE, { params: { code } })),
});
export default connect(select, perform)(CustomRewardCard);
export default connect(
select,
perform
)(CustomRewardCard);

View file

@ -10,7 +10,7 @@ import rewardStyle from '../../styles/reward';
class CustomRewardCard extends React.PureComponent<Props> {
state = {
claimStarted: false,
rewardCode: ''
rewardCode: '',
};
componentWillReceiveProps(nextProps) {
@ -49,31 +49,39 @@ class CustomRewardCard extends React.PureComponent<Props> {
this.setState({ claimStarted: true }, () => {
submitRewardCode(rewardCode);
});
}
};
render() {
const { canClaim, rewardIsPending } = this.props;
return (
<View style={[rewardStyle.rewardCard, rewardStyle.row]} >
<View style={[rewardStyle.rewardCard, rewardStyle.row]}>
<View style={rewardStyle.leftCol}>
{rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
</View>
<View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>Custom Code</Text>
<Text style={rewardStyle.rewardDescription}>Are you a supermodel or rockstar that received a custom reward code? Claim it here.</Text>
<Text style={rewardStyle.rewardDescription}>
Are you a supermodel or rockstar that received a custom reward code? Claim it here.
</Text>
<View>
<TextInput style={rewardStyle.customCodeInput}
placeholder={"0123abc"}
onChangeText={text => this.setState({ rewardCode: text })}
value={this.state.rewardCode} />
<Button style={rewardStyle.redeemButton}
text={"Redeem"}
disabled={(!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending)}
onPress={() => {
if (!rewardIsPending) { this.onClaimPress(); }
}} />
<TextInput
style={rewardStyle.customCodeInput}
placeholder={'0123abc'}
onChangeText={text => this.setState({ rewardCode: text })}
value={this.state.rewardCode}
/>
<Button
style={rewardStyle.redeemButton}
text={'Redeem'}
disabled={!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending}
onPress={() => {
if (!rewardIsPending) {
this.onClaimPress();
}
}}
/>
</View>
</View>
<View style={rewardStyle.rightCol}>
@ -83,6 +91,6 @@ class CustomRewardCard extends React.PureComponent<Props> {
</View>
);
}
};
}
export default CustomRewardCard;

View file

@ -29,7 +29,11 @@ class DateTime extends React.PureComponent<Props> {
const locale = 'en-US'; // default to en-US until we get a working i18n module for RN
if (timeAgo) {
return date ? <View style={style}><Text style={textStyle}>{moment(date).from(moment())}</Text></View> : null;
return date ? (
<View style={style}>
<Text style={textStyle}>{moment(date).from(moment())}</Text>
</View>
) : null;
}
// TODO: formatOptions not working as expected in RN
@ -38,13 +42,9 @@ class DateTime extends React.PureComponent<Props> {
return (
<View style={style}>
<Text style={textStyle}>
{date &&
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) &&
moment(date).format('MMMM D, YYYY')}
{date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && moment(date).format('MMMM D, YYYY')}
{show === DateTime.SHOW_BOTH && ' '}
{date &&
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) &&
date.toLocaleTimeString()}
{date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()}
{!date && '...'}
</Text>
</View>

View file

@ -14,7 +14,7 @@ class DrawerContent extends React.PureComponent {
<SafeAreaView style={discoverStyle.drawerContentContainer} forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems
{...props}
onItemPress={(route) => {
onItemPress={route => {
const { routeName } = route.route;
if (Constants.FULL_ROUTE_NAME_DISCOVER === routeName) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_DISCOVER });
@ -28,7 +28,7 @@ class DrawerContent extends React.PureComponent {
onItemPress(route);
}}
/>
/>
</SafeAreaView>
</ScrollView>
);

View file

@ -22,4 +22,7 @@ const perform = dispatch => ({
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
});
export default connect(select, perform)(FileDownloadButton);
export default connect(
select,
perform
)(FileDownloadButton);

View file

@ -49,13 +49,14 @@ class FileDownloadButton extends React.PureComponent {
} = this.props;
if ((fileInfo && !fileInfo.stopped) || loading || downloading) {
const progress =
fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...';
return (
<View style={[style, fileDownloadButtonStyle.container]}>
<View style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}></View>
<View
style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}
></View>
<Text style={fileDownloadButtonStyle.text}>{label}</Text>
</View>
);
@ -68,29 +69,35 @@ class FileDownloadButton extends React.PureComponent {
);
}
return (
<Button icon={isPlayable ? 'play' : null}
text={(isPlayable ? 'Play' : (isViewable ? 'View' : 'Download'))}
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]} onPress={() => {
if (NativeModules.Firebase) {
NativeModules.Firebase.track('purchase_uri', { uri: uri });
}
purchaseUri(uri, costInfo, !isPlayable);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.checkDownloads();
}
if (isPlayable && onPlay) {
this.props.onPlay();
}
if (isViewable && onView) {
this.props.onView();
}
}} />
<Button
icon={isPlayable ? 'play' : null}
text={isPlayable ? 'Play' : isViewable ? 'View' : 'Download'}
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={() => {
if (NativeModules.Firebase) {
NativeModules.Firebase.track('purchase_uri', { uri: uri });
}
purchaseUri(uri, costInfo, !isPlayable);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.checkDownloads();
}
if (isPlayable && onPlay) {
this.props.onPlay();
}
if (isViewable && onView) {
this.props.onView();
}
}}
/>
);
} else if (fileInfo && fileInfo.download_path) {
return (
<TouchableOpacity onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]} onPress={openFile}>
<TouchableOpacity
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={openFile}
>
<Text style={fileDownloadButtonStyle.text}>{isViewable ? 'View' : 'Open'}</Text>
</TouchableOpacity>
);

View file

@ -7,7 +7,7 @@ import {
makeSelectThumbnailForUri,
makeSelectTitleForUri,
makeSelectIsUriResolving,
makeSelectClaimIsNsfw
makeSelectClaimIsNsfw,
} from 'lbry-redux';
import { selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
@ -29,4 +29,7 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
});
export default connect(select, perform)(FileItem);
export default connect(
select,
perform
)(FileItem);

View file

@ -40,7 +40,7 @@ class FileItem extends React.PureComponent {
NativeModules.Firebase.track('explore_click', { uri: normalizedUri });
}
navigateToUri(navigation, normalizedUri);
}
};
render() {
const {
@ -56,49 +56,76 @@ class FileItem extends React.PureComponent {
navigation,
showDetails,
compactView,
titleBeforeThumbnail
titleBeforeThumbnail,
} = this.props;
const uri = normalizeURI(this.props.uri);
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const channelName = claim ? claim.channel_name : null;
const channelClaimId = claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
const channelClaimId =
claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
const height = claim ? claim.height : null;
return (
<View style={style}>
<TouchableOpacity style={discoverStyle.container} onPress={this.navigateToFileUri}>
{!compactView && titleBeforeThumbnail && <Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>}
<FileItemMedia title={title}
thumbnail={thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
isResolvingUri={isResolvingUri}
style={mediaStyle} />
{!compactView && titleBeforeThumbnail && (
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
{title}
</Text>
)}
<FileItemMedia
title={title}
thumbnail={thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
isResolvingUri={isResolvingUri}
style={mediaStyle}
/>
{(!compactView && fileInfo && fileInfo.completed && fileInfo.download_path) &&
<Icon style={discoverStyle.downloadedIcon} solid={true} color={Colors.NextLbryGreen} name={"folder"} size={16} />}
{(!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path)) &&
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />}
{!compactView && <View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={14} />}
</View>}
{(!compactView && showDetails) &&
<View style={discoverStyle.detailsRow}>
{channelName &&
<Link style={discoverStyle.channelName} text={channelName} onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}} />}
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
</View>}
{!compactView && fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon
style={discoverStyle.downloadedIcon}
solid={true}
color={Colors.NextLbryGreen}
name={'folder'}
size={16}
/>
)}
{!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
)}
{!compactView && (
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
{title}
</Text>
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={14} />}
</View>
)}
{!compactView && showDetails && (
<View style={discoverStyle.detailsRow}>
{channelName && (
<Link
style={discoverStyle.channelName}
text={channelName}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}}
/>
)}
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
</View>
)}
</TouchableOpacity>
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
{obscureNsfw && (
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
)}
</View>
);
}
}
export default FileItem;
export default FileItem;

View file

@ -4,4 +4,7 @@ import FileItemMedia from './view';
const select = state => ({});
const perform = dispatch => ({});
export default connect(select, perform)(FileItemMedia);
export default connect(
select,
perform
)(FileItemMedia);

View file

@ -1,7 +1,7 @@
import React from 'react';
import { ActivityIndicator, Image, Text, View } from 'react-native';
import Colors from 'styles/colors';
import FastImage from 'react-native-fast-image'
import FastImage from 'react-native-fast-image';
import fileItemMediaStyle from 'styles/fileItemMedia';
class FileItemMedia extends React.PureComponent {
@ -20,33 +20,31 @@ class FileItemMedia extends React.PureComponent {
];
state: {
imageLoadFailed: false
imageLoadFailed: false,
};
componentWillMount() {
this.setState({
autoThumbStyle:
FileItemMedia.AUTO_THUMB_STYLES[
Math.floor(Math.random() * FileItemMedia.AUTO_THUMB_STYLES.length)
],
FileItemMedia.AUTO_THUMB_STYLES[Math.floor(Math.random() * FileItemMedia.AUTO_THUMB_STYLES.length)],
});
}
getFastImageResizeMode(resizeMode) {
switch (resizeMode) {
case "contain":
case 'contain':
return FastImage.resizeMode.contain;
case "stretch":
case 'stretch':
return FastImage.resizeMode.stretch;
case "center":
case 'center':
return FastImage.resizeMode.center;
default:
return FastImage.resizeMode.cover;
}
}
isThumbnailValid = (thumbnail) => {
if (!thumbnail || ((typeof thumbnail) !== 'string')) {
isThumbnailValid = thumbnail => {
if (!thumbnail || typeof thumbnail !== 'string') {
return false;
}
@ -55,7 +53,7 @@ class FileItemMedia extends React.PureComponent {
}
return true;
}
};
render() {
let style = this.props.style;
@ -70,16 +68,17 @@ class FileItemMedia extends React.PureComponent {
// No blur radius support in FastImage yet
return (
<Image
source={{uri: thumbnail}}
source={{ uri: thumbnail }}
blurRadius={blurRadius}
resizeMode={resizeMode ? resizeMode : "cover"}
resizeMode={resizeMode ? resizeMode : 'cover'}
style={style}
/>);
/>
);
}
return (
<FastImage
source={{uri: thumbnail}}
source={{ uri: thumbnail }}
onError={() => this.setState({ imageLoadFailed: true })}
resizeMode={this.getFastImageResizeMode(resizeMode)}
style={style}
@ -91,15 +90,19 @@ class FileItemMedia extends React.PureComponent {
<View style={[style ? style : fileItemMediaStyle.autothumb, atStyle]}>
{isResolvingUri && (
<View style={fileItemMediaStyle.resolving}>
<ActivityIndicator color={Colors.White} size={"large"} />
<ActivityIndicator color={Colors.White} size={'large'} />
<Text style={fileItemMediaStyle.text}>Resolving...</Text>
</View>
)}
{!isResolvingUri && <Text style={fileItemMediaStyle.autothumbText}>{title &&
title
.replace(/\s+/g, '')
.substring(0, Math.min(title.replace(' ', '').length, 5))
.toUpperCase()}</Text>}
{!isResolvingUri && (
<Text style={fileItemMediaStyle.autothumbText}>
{title &&
title
.replace(/\s+/g, '')
.substring(0, Math.min(title.replace(' ', '').length, 5))
.toUpperCase()}
</Text>
)}
</View>
);
}

View file

@ -8,4 +8,7 @@ const select = state => ({
const perform = dispatch => ({});
export default connect(select, perform)(FileList);
export default connect(
select,
perform
)(FileList);

View file

@ -145,15 +145,7 @@ class FileList extends React.PureComponent<Props, State> {
sortFunctions: {};
render() {
const {
contentContainerStyle,
fileInfos,
hideFilter,
checkPending,
navigation,
onEndReached,
style
} = this.props;
const { contentContainerStyle, fileInfos, hideFilter, checkPending, navigation, onEndReached, style } = this.props;
const { sortBy } = this.state;
const items = [];
@ -182,13 +174,16 @@ class FileList extends React.PureComponent<Props, State> {
data={items}
onEndReached={onEndReached}
keyExtractor={(item, index) => item}
renderItem={({item}) => (
<FileItem style={fileListStyle.fileItem}
uri={item}
navigation={navigation}
showDetails={true}
compactView={false} />
)} />
renderItem={({ item }) => (
<FileItem
style={fileListStyle.fileItem}
uri={item}
navigation={navigation}
showDetails={true}
compactView={false}
/>
)}
/>
);
}
}

View file

@ -23,7 +23,10 @@ const select = (state, props) => ({
});
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri))
resolveUri: uri => dispatch(doResolveUri(uri)),
});
export default connect(select, perform)(FileListItem);
export default connect(
select,
perform
)(FileListItem);

View file

@ -1,12 +1,6 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import {
ActivityIndicator,
Platform,
Text,
TouchableOpacity,
View
} from 'react-native';
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri, formatBytes } from 'utils/helper';
import Colors from 'styles/colors';
import DateTime from 'component/dateTime';
@ -18,7 +12,7 @@ import ProgressBar from 'component/progressBar';
import fileListStyle from 'styles/fileList';
class FileListItem extends React.PureComponent {
getStorageForFileInfo = (fileInfo) => {
getStorageForFileInfo = fileInfo => {
if (!fileInfo.completed) {
const written = formatBytes(fileInfo.written_bytes);
const total = formatBytes(fileInfo.total_bytes);
@ -26,19 +20,19 @@ class FileListItem extends React.PureComponent {
}
return formatBytes(fileInfo.written_bytes);
}
};
formatTitle = (title) => {
formatTitle = title => {
if (!title) {
return title;
}
return (title.length > 80) ? title.substring(0, 77).trim() + '...' : title;
}
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
};
getDownloadProgress = (fileInfo) => {
getDownloadProgress = fileInfo => {
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
}
};
componentDidMount() {
const { claim, resolveUri, uri } = this.props;
@ -59,7 +53,7 @@ class FileListItem extends React.PureComponent {
onPress,
navigation,
thumbnail,
title
title,
} = this.props;
const uri = normalizeURI(this.props.uri);
@ -82,50 +76,80 @@ class FileListItem extends React.PureComponent {
return (
<View style={style}>
<TouchableOpacity style={style} onPress={onPress}>
<FileItemMedia style={fileListStyle.thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
title={(title || name)}
thumbnail={thumbnail} />
{(fileInfo && fileInfo.completed && fileInfo.download_path) &&
<Icon style={fileListStyle.downloadedIcon} solid={true} color={Colors.NextLbryGreen} name={"folder"} size={16} />}
<FileItemMedia
style={fileListStyle.thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
title={title || name}
thumbnail={thumbnail}
/>
{fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon
style={fileListStyle.downloadedIcon}
solid={true}
color={Colors.NextLbryGreen}
name={'folder'}
size={16}
/>
)}
<View style={fileListStyle.detailsContainer}>
{featuredResult && <Text style={fileListStyle.featuredUri} numberOfLines={1}>{uri}</Text>}
{featuredResult && (
<Text style={fileListStyle.featuredUri} numberOfLines={1}>
{uri}
</Text>
)}
{!title && !name && !channel && isResolving && (
<View>
{(!title && !name) && <Text style={fileListStyle.uri}>{uri}</Text>}
{(!title && !name) && <View style={fileListStyle.row}>
<ActivityIndicator size={"small"} color={featuredResult ? Colors.White : Colors.LbryGreen} />
</View>}
</View>)}
<View>
{!title && !name && <Text style={fileListStyle.uri}>{uri}</Text>}
{!title && !name && (
<View style={fileListStyle.row}>
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.LbryGreen} />
</View>
)}
</View>
)}
{(title || name) && <Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>{this.formatTitle(title) || this.formatTitle(name)}</Text>}
{channel &&
<Link style={fileListStyle.publisher} text={channel} onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}} />}
{(title || name) && (
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
{this.formatTitle(title) || this.formatTitle(name)}
</Text>
)}
{channel && (
<Link
style={fileListStyle.publisher}
text={channel}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}}
/>
)}
<View style={fileListStyle.info}>
{(fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0) &&
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>}
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>
)}
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
</View>
{(fileInfo && fileInfo.download_path) &&
{fileInfo && fileInfo.download_path && (
<View style={fileListStyle.downloadInfo}>
{!fileInfo.completed &&
{!fileInfo.completed && (
<ProgressBar
borderRadius={3}
color={Colors.NextLbryGreen}
height={3}
style={fileListStyle.progress}
progress={this.getDownloadProgress(fileInfo)} />}
progress={this.getDownloadProgress(fileInfo)}
/>
)}
</View>
}
)}
</View>
</TouchableOpacity>
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
{obscureNsfw && (
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
)}
</View>
);
}

View file

@ -13,4 +13,7 @@ const perform = dispatch => ({
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
});
export default connect(select, perform)(FilePrice);
export default connect(
select,
perform
)(FilePrice);

View file

@ -46,9 +46,7 @@ class CreditAmount extends React.PureComponent {
} else {
if (this.props.label) {
const label =
typeof this.props.label === 'string'
? this.props.label
: parseFloat(amount) == 1 ? 'credit' : 'credits';
typeof this.props.label === 'string' ? this.props.label : parseFloat(amount) == 1 ? 'credit' : 'credits';
amountText = `${formattedAmount} ${label}`;
} else {
@ -67,9 +65,7 @@ class CreditAmount extends React.PureComponent {
*
</span>
) : null}*/
return (
<Text style={style}>{amountText}</Text>
);
return <Text style={style}>{amountText}</Text>;
}
}
@ -100,7 +96,7 @@ class FilePrice extends React.PureComponent {
<View style={style}>
<Text style={textStyle}>???</Text>
</View>
)
);
}
return (
@ -111,10 +107,13 @@ class FilePrice extends React.PureComponent {
amount={parseFloat(costInfo.cost)}
isEstimate={isEstimate}
showFree
showFullPrice={showFullPrice}>???</CreditAmount>
showFullPrice={showFullPrice}
>
???
</CreditAmount>
</View>
);
}
}
export default FilePrice;
export default FilePrice;

View file

@ -11,4 +11,7 @@ const select = state => ({
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
});
export default connect(select, null)(FloatingWalletBalance);
export default connect(
select,
null
)(FloatingWalletBalance);

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import { formatCredits } from 'lbry-redux'
import { formatCredits } from 'lbry-redux';
import Address from 'component/address';
import Button from 'component/button';
import Colors from 'styles/colors';
@ -18,17 +18,23 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
return (
<View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}>
{(!rewardsNotInterested && unclaimedRewardAmount > 0) &&
<TouchableOpacity style={floatingButtonStyle.pendingContainer}
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} >
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
</TouchableOpacity>}
<TouchableOpacity style={floatingButtonStyle.container}
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}>
{!rewardsNotInterested && unclaimedRewardAmount > 0 && (
<TouchableOpacity
style={floatingButtonStyle.pendingContainer}
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })}
>
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
</TouchableOpacity>
)}
<TouchableOpacity
style={floatingButtonStyle.container}
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}
>
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
{(!isNaN(balance) || balance === 0) && (
<Text style={floatingButtonStyle.text}>{(formatCredits(parseFloat(balance), 2) + ' LBC')}</Text>)}
<Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 2) + ' LBC'}</Text>
)}
</TouchableOpacity>
</View>
);

View file

@ -1,9 +1,7 @@
import React from 'react';
class Gallery extends React.PureComponent {
render() {
}
render() {}
}
export default Gallery;

View file

@ -3,7 +3,10 @@ import { doToast } from 'lbry-redux';
import Link from './view';
const perform = dispatch => ({
notify: (data) => dispatch(doToast(data))
notify: data => dispatch(doToast(data)),
});
export default connect(null, perform)(Link);
export default connect(
null,
perform
)(Link);

View file

@ -2,13 +2,12 @@ import React from 'react';
import { Linking, Text, TouchableOpacity } from 'react-native';
export default class Link extends React.PureComponent {
constructor(props) {
super(props)
super(props);
this.state = {
tappedStyle: false,
}
this.addTappedStyle = this.addTappedStyle.bind(this)
};
this.addTappedStyle = this.addTappedStyle.bind(this);
}
handlePress = () => {
@ -19,28 +18,27 @@ export default class Link extends React.PureComponent {
} else {
if (this.props.effectOnTap) this.addTappedStyle();
Linking.openURL(href)
.then(() => setTimeout(() => { this.setState({ tappedStyle: false }); }, 2000))
.catch(err => {
notify({ message: error, isError: true })
this.setState({tappedStyle: false})
}
);
.then(() =>
setTimeout(() => {
this.setState({ tappedStyle: false });
}, 2000)
)
.catch(err => {
notify({ message: error, isError: true });
this.setState({ tappedStyle: false });
});
}
}
};
addTappedStyle() {
this.setState({ tappedStyle: true });
setTimeout(() => { this.setState({ tappedStyle: false }); }, 2000);
setTimeout(() => {
this.setState({ tappedStyle: false });
}, 2000);
}
render() {
const {
ellipsizeMode,
numberOfLines,
onPress,
style,
text
} = this.props;
const { ellipsizeMode, numberOfLines, onPress, style, text } = this.props;
let styles = [];
if (style) {
@ -60,9 +58,10 @@ export default class Link extends React.PureComponent {
style={styles}
numberOfLines={numberOfLines}
ellipsizeMode={ellipsizeMode}
onPress={onPress ? onPress : this.handlePress}>
onPress={onPress ? onPress : this.handlePress}
>
{text}
</Text>
);
}
};
}

View file

@ -15,4 +15,7 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)),
});
export default connect(select, perform)(MediaPlayer);
export default connect(
select,
perform
)(MediaPlayer);

View file

@ -9,16 +9,16 @@ import {
Text,
View,
ScrollView,
TouchableOpacity
TouchableOpacity,
} from 'react-native';
import Colors from 'styles/colors';
import FastImage from 'react-native-fast-image'
import FastImage from 'react-native-fast-image';
import Video from 'react-native-video';
import Icon from 'react-native-vector-icons/FontAwesome5';
import FileItemMedia from 'component/fileItemMedia';
import mediaPlayerStyle from 'styles/mediaPlayer';
const positionSaveInterval = 10
const positionSaveInterval = 10;
class MediaPlayer extends React.PureComponent {
static ControlsTimeout = 3000;
@ -52,13 +52,15 @@ class MediaPlayer extends React.PureComponent {
seekerOffset: 0,
seekerPosition: 0,
firstPlay: true,
seekTimeout: -1
seekTimeout: -1,
};
}
formatTime(time) {
let str = '';
let minutes = 0, hours = 0, seconds = parseInt(time, 10);
let minutes = 0,
hours = 0,
seconds = parseInt(time, 10);
if (seconds > 60) {
minutes = parseInt(seconds / 60, 10);
seconds = seconds % 60;
@ -84,9 +86,9 @@ class MediaPlayer extends React.PureComponent {
return value;
}
onLoad = (data) => {
onLoad = data => {
this.setState({
duration: data.duration
duration: data.duration,
});
const { position } = this.props;
@ -98,9 +100,9 @@ class MediaPlayer extends React.PureComponent {
if (this.props.onMediaLoaded) {
this.props.onMediaLoaded();
}
}
};
onProgress = (data) => {
onProgress = data => {
const { savePosition, claim } = this.props;
this.setState({ buffering: false, currentTime: data.currentTime });
@ -121,13 +123,13 @@ class MediaPlayer extends React.PureComponent {
this.hidePlayerControls();
}
}
};
clearControlsTimeout = () => {
if (this.state.controlsTimeout > -1) {
clearTimeout(this.state.controlsTimeout)
clearTimeout(this.state.controlsTimeout);
}
}
};
showPlayerControls = () => {
this.clearControlsTimeout();
@ -135,12 +137,12 @@ class MediaPlayer extends React.PureComponent {
this.setState({ areControlsVisible: true });
}
this.hidePlayerControls();
}
};
manualHidePlayerControls = () => {
this.clearControlsTimeout();
this.setState({ areControlsVisible: false });
}
};
hidePlayerControls() {
const player = this;
@ -161,19 +163,19 @@ class MediaPlayer extends React.PureComponent {
} else {
this.showPlayerControls();
}
}
};
togglePlay = () => {
this.showPlayerControls();
this.setState({ paused: !this.state.paused }, this.handlePausedState);
}
};
handlePausedState = () => {
if (!this.state.paused) {
// onProgress will automatically clear this, so it's fine
this.setState({ buffering: true });
}
}
};
toggleFullscreenMode = () => {
this.showPlayerControls();
@ -183,7 +185,7 @@ class MediaPlayer extends React.PureComponent {
onFullscreenToggled(this.state.fullscreenMode);
}
});
}
};
onEnd = () => {
this.setState({ paused: true });
@ -191,7 +193,7 @@ class MediaPlayer extends React.PureComponent {
this.props.onPlaybackFinished();
}
this.video.seek(0);
}
};
setSeekerPosition(position = 0) {
position = this.checkSeekerPosition(position);
@ -244,10 +246,14 @@ class MediaPlayer extends React.PureComponent {
this.onEnd();
} else {
this.seekTo(time);
this.setState({ seekTimeout: setTimeout(() => { this.setState({ seeking: false }); }, 100) });
this.setState({
seekTimeout: setTimeout(() => {
this.setState({ seeking: false });
}, 100),
});
}
this.hidePlayerControls();
}
},
});
}
@ -268,7 +274,7 @@ class MediaPlayer extends React.PureComponent {
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
}
return 0;
};
}
componentWillMount() {
this.initSeeker();
@ -283,7 +289,7 @@ class MediaPlayer extends React.PureComponent {
}
componentDidMount() {
const { assignPlayer, backgroundPlayEnabled } = this.props;
const { assignPlayer, backgroundPlayEnabled } = this.props;
if (assignPlayer) {
assignPlayer(this);
}
@ -319,21 +325,21 @@ class MediaPlayer extends React.PureComponent {
this.setState({ paused: false, autoPaused: false });
}
}
}
};
onBuffer = () => {
if (!this.state.paused) {
this.setState({ buffering: true }, () => this.manualHidePlayerControls());
}
}
};
play = () => {
this.setState({ paused: false }, this.updateBackgroundMediaNotification);
}
};
pause = () => {
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
}
};
updateBackgroundMediaNotification = () => {
this.handlePausedState();
@ -344,7 +350,7 @@ class MediaPlayer extends React.PureComponent {
NativeModules.BackgroundMedia.showPlaybackNotification(title, channel, uri, this.state.paused);
}
}
}
};
renderPlayerControls() {
const { onBackButtonPressed } = this.props;
@ -353,11 +359,10 @@ class MediaPlayer extends React.PureComponent {
return (
<View style={mediaPlayerStyle.playerControlsContainer}>
<TouchableOpacity style={mediaPlayerStyle.backButton} onPress={onBackButtonPressed}>
<Icon name={"arrow-left"} size={18} style={mediaPlayerStyle.backButtonIcon} />
</TouchableOpacity>
<Icon name={'arrow-left'} size={18} style={mediaPlayerStyle.backButtonIcon} />
</TouchableOpacity>
<TouchableOpacity style={mediaPlayerStyle.playPauseButton}
onPress={this.togglePlay}>
<TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
{this.state.paused && <Icon name="play" size={40} color="#ffffff" />}
{!this.state.paused && <Icon name="pause" size={40} color="#ffffff" />}
</TouchableOpacity>
@ -376,7 +381,7 @@ class MediaPlayer extends React.PureComponent {
return null;
}
onSeekerTouchAreaPressed = (evt) => {
onSeekerTouchAreaPressed = evt => {
if (evt && evt.nativeEvent) {
const newSeekerPosition = evt.nativeEvent.locationX;
if (!isNaN(newSeekerPosition)) {
@ -385,13 +390,13 @@ class MediaPlayer extends React.PureComponent {
this.seekTo(time);
}
}
}
};
onTrackingLayout = (evt) => {
onTrackingLayout = evt => {
this.trackingOffset = evt.nativeEvent.layout.x;
this.seekerWidth = evt.nativeEvent.layout.width;
this.setSeekerPosition(this.calculateSeekerPosition());
}
};
render() {
const { onLayout, source, style, thumbnail } = this.props;
@ -406,64 +411,90 @@ class MediaPlayer extends React.PureComponent {
}
}
const trackingStyle = [mediaPlayerStyle.trackingControls, this.state.fullscreenMode ?
mediaPlayerStyle.fullscreenTrackingControls : mediaPlayerStyle.containedTrackingControls];
const trackingStyle = [
mediaPlayerStyle.trackingControls,
this.state.fullscreenMode
? mediaPlayerStyle.fullscreenTrackingControls
: mediaPlayerStyle.containedTrackingControls,
];
return (
<View style={styles} onLayout={onLayout}>
<Video source={{ uri: source }}
bufferConfig={{ minBufferMs: 15000, maxBufferMs: 60000, bufferForPlaybackMs: 5000, bufferForPlaybackAfterRebufferMs: 5000 }}
ref={(ref: Video) => { this.video = ref; }}
resizeMode={this.state.resizeMode}
playInBackground={this.state.backgroundPlayEnabled}
style={mediaPlayerStyle.player}
rate={this.state.rate}
volume={this.state.volume}
paused={this.state.paused}
onLoad={this.onLoad}
onBuffer={this.onBuffer}
onProgress={this.onProgress}
onEnd={this.onEnd}
onError={this.onError}
minLoadRetryCount={999}
/>
<Video
source={{ uri: source }}
bufferConfig={{
minBufferMs: 15000,
maxBufferMs: 60000,
bufferForPlaybackMs: 5000,
bufferForPlaybackAfterRebufferMs: 5000,
}}
ref={(ref: Video) => {
this.video = ref;
}}
resizeMode={this.state.resizeMode}
playInBackground={this.state.backgroundPlayEnabled}
style={mediaPlayerStyle.player}
rate={this.state.rate}
volume={this.state.volume}
paused={this.state.paused}
onLoad={this.onLoad}
onBuffer={this.onBuffer}
onProgress={this.onProgress}
onEnd={this.onEnd}
onError={this.onError}
minLoadRetryCount={999}
/>
{this.state.firstPlay && thumbnail && thumbnail.trim().length > 0 &&
<FastImage
source={{uri: thumbnail}}
resizeMode={FastImage.resizeMode.cover}
style={mediaPlayerStyle.playerThumbnail}
/>}
{this.state.firstPlay && thumbnail && thumbnail.trim().length > 0 && (
<FastImage
source={{ uri: thumbnail }}
resizeMode={FastImage.resizeMode.cover}
style={mediaPlayerStyle.playerThumbnail}
/>
)}
<TouchableOpacity style={mediaPlayerStyle.playerControls} onPress={this.togglePlayerControls}>
{this.renderPlayerControls()}
</TouchableOpacity>
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) &&
<View style={trackingStyle} onLayout={this.onTrackingLayout}>
<View style={mediaPlayerStyle.progress}>
<View style={[mediaPlayerStyle.innerProgressCompleted, { width: completedWidth }]} />
<View style={[mediaPlayerStyle.innerProgressRemaining, { width: remainingWidth }]} />
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) && (
<View style={trackingStyle} onLayout={this.onTrackingLayout}>
<View style={mediaPlayerStyle.progress}>
<View style={[mediaPlayerStyle.innerProgressCompleted, { width: completedWidth }]} />
<View style={[mediaPlayerStyle.innerProgressRemaining, { width: remainingWidth }]} />
</View>
</View>
</View>}
)}
{this.state.buffering &&
<View style={mediaPlayerStyle.loadingContainer}>
<ActivityIndicator color={Colors.LbryGreen} size="large" />
</View>}
{this.state.areControlsVisible &&
<View style={{ left: this.getTrackingOffset(), width: this.seekerWidth }}>
<View style={[mediaPlayerStyle.seekerHandle,
(this.state.fullscreenMode ? mediaPlayerStyle.seekerHandleFs : mediaPlayerStyle.seekerHandleContained),
{ left: this.state.seekerPosition }]} { ...this.seekResponder.panHandlers }>
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />
{this.state.buffering && (
<View style={mediaPlayerStyle.loadingContainer}>
<ActivityIndicator color={Colors.LbryGreen} size="large" />
</View>
<TouchableOpacity
style={[mediaPlayerStyle.seekerTouchArea,
(this.state.fullscreenMode ? mediaPlayerStyle.seekerTouchAreaFs : mediaPlayerStyle.seekerTouchAreaContained)]}
onPress={this.onSeekerTouchAreaPressed} />
</View>}
)}
{this.state.areControlsVisible && (
<View style={{ left: this.getTrackingOffset(), width: this.seekerWidth }}>
<View
style={[
mediaPlayerStyle.seekerHandle,
this.state.fullscreenMode ? mediaPlayerStyle.seekerHandleFs : mediaPlayerStyle.seekerHandleContained,
{ left: this.state.seekerPosition },
]}
{...this.seekResponder.panHandlers}
>
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />
</View>
<TouchableOpacity
style={[
mediaPlayerStyle.seekerTouchArea,
this.state.fullscreenMode
? mediaPlayerStyle.seekerTouchAreaFs
: mediaPlayerStyle.seekerTouchAreaContained,
]}
onPress={this.onSeekerTouchAreaPressed}
/>
</View>
)}
</View>
);
}

View file

@ -2,7 +2,6 @@ import React from 'react';
import Icon from 'react-native-vector-icons/FontAwesome5';
import { TouchableOpacity } from 'react-native';
class NavigationButton extends React.PureComponent {
render() {
const { iconStyle, name, onPress, size, style } = this.props;
@ -13,6 +12,6 @@ class NavigationButton extends React.PureComponent {
</TouchableOpacity>
);
}
};
}
export default NavigationButton;

View file

@ -3,4 +3,7 @@ import NsfwOverlay from './view';
const perform = dispatch => ({});
export default connect(null, perform)(NsfwOverlay);
export default connect(
null,
perform
)(NsfwOverlay);

View file

@ -6,9 +6,11 @@ class NsfwOverlay extends React.PureComponent {
render() {
return (
<TouchableOpacity style={discoverStyle.overlay} activeOpacity={0.95} onPress={this.props.onPress}>
<Text style={discoverStyle.overlayText}>This content is Not Safe For Work. To view adult content, please change your Settings.</Text>
<Text style={discoverStyle.overlayText}>
This content is Not Safe For Work. To view adult content, please change your Settings.
</Text>
</TouchableOpacity>
)
);
}
}

View file

@ -3,4 +3,7 @@ import PageHeader from './view';
const perform = dispatch => ({});
export default connect(null, perform)(PageHeader);
export default connect(
null,
perform
)(PageHeader);

View file

@ -1,13 +1,6 @@
// Based on https://github.com/react-navigation/react-navigation/blob/master/src/views/Header/Header.js
import React from 'react';
import {
Animated,
Platform,
StyleSheet,
Text,
TouchableOpacity,
View
} from 'react-native';
import { Animated, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import NavigationButton from 'component/navigationButton';
import pageHeaderStyle from 'styles/pageHeader';
@ -18,20 +11,14 @@ const AnimatedText = Animated.Text;
class PageHeader extends React.PureComponent {
render() {
const { title, onBackPressed } = this.props;
const containerStyles = [
pageHeaderStyle.container,
{ height: APPBAR_HEIGHT }
];
const containerStyles = [pageHeaderStyle.container, { height: APPBAR_HEIGHT }];
return (
<View style={containerStyles}>
<View style={pageHeaderStyle.flexOne}>
<View style={pageHeaderStyle.header}>
<View style={pageHeaderStyle.title}>
<AnimatedText
numberOfLines={1}
style={pageHeaderStyle.titleText}
accessibilityTraits="header">
<AnimatedText numberOfLines={1} style={pageHeaderStyle.titleText} accessibilityTraits="header">
{title}
</AnimatedText>
</View>

View file

@ -19,7 +19,7 @@ class ProgressBar extends React.PureComponent {
return new Error('progress should be between 0 and 100');
}
},
style: PropTypes.any
style: PropTypes.any,
};
render() {
@ -39,13 +39,15 @@ class ProgressBar extends React.PureComponent {
borderRadius: borderRadius || defaultBorderRadius,
flexDirection: 'row',
height: height || defaultHeight,
overflow: 'hidden'
overflow: 'hidden',
});
return (
<View style={styles}>
<View style={{ backgroundColor: color, borderRadius: borderRadius || defaultBorderRadius, flex: currentProgress }} />
<View style={{ backgroundColor: color, opacity: 0.2, flex: (100 - currentProgress) }} />
<View
style={{ backgroundColor: color, borderRadius: borderRadius || defaultBorderRadius, flex: currentProgress }}
/>
<View style={{ backgroundColor: color, opacity: 0.2, flex: 100 - currentProgress }} />
</View>
);
}

View file

@ -22,4 +22,4 @@ const perform = dispatch => ({
export default connect(
select,
perform
)(RelatedContent);
)(RelatedContent);

View file

@ -50,16 +50,18 @@ export default class RelatedContent extends React.PureComponent<Props> {
return (
<View style={relatedContentStyle.container}>
<Text style={relatedContentStyle.title}>Related Content</Text>
{recommendedContent && recommendedContent.map(recommendedUri => (
<FileListItem
style={fileListStyle.item}
key={recommendedUri}
uri={recommendedUri}
navigation={navigation}
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })} />
))}
{recommendedContent &&
recommendedContent.map(recommendedUri => (
<FileListItem
style={fileListStyle.item}
key={recommendedUri}
uri={recommendedUri}
navigation={navigation}
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })}
/>
))}
{isSearching && <ActivityIndicator size="small" color={Colors.LbryGreen} style={relatedContentStyle.loading} />}
</View>
);
}
}
}

View file

@ -23,7 +23,10 @@ const makeSelect = () => {
const perform = dispatch => ({
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
clearError: reward => dispatch(doClaimRewardClearError(reward)),
notify: data => dispatch(doToast(data))
notify: data => dispatch(doToast(data)),
});
export default connect(makeSelect, perform)(RewardCard);
export default connect(
makeSelect,
perform
)(RewardCard);

View file

@ -7,7 +7,7 @@ import Link from '../link';
import rewardStyle from '../../styles/reward';
type Props = {
canClaim: bool,
canClaim: boolean,
onClaimPress: object,
reward: {
id: string,
@ -22,7 +22,7 @@ type Props = {
class RewardCard extends React.PureComponent<Props> {
state = {
claimStarted: false
claimStarted: false,
};
componentWillReceiveProps(nextProps) {
@ -40,13 +40,7 @@ class RewardCard extends React.PureComponent<Props> {
}
onClaimPress = () => {
const {
canClaim,
claimReward,
notify,
reward,
showVerification
} = this.props;
const { canClaim, claimReward, notify, reward, showVerification } = this.props;
if (!canClaim) {
if (showVerification) {
@ -59,37 +53,52 @@ class RewardCard extends React.PureComponent<Props> {
this.setState({ claimStarted: true }, () => {
claimReward(reward);
});
}
};
render() {
const { canClaim, isPending, onClaimPress, reward } = this.props;
const claimed = !!reward.transaction_id;
return (
<TouchableOpacity style={[rewardStyle.rewardCard, rewardStyle.row]} onPress={() => {
if (!isPending && !claimed) {
this.onClaimPress();
}
}}>
<TouchableOpacity
style={[rewardStyle.rewardCard, rewardStyle.row]}
onPress={() => {
if (!isPending && !claimed) {
this.onClaimPress();
}
}}
>
<View style={rewardStyle.leftCol}>
{!isPending && <TouchableOpacity onPress={() => {
if (!claimed) {
this.onClaimPress();
}
}}>
{claimed && <Icon name={claimed ? "check-circle" : "circle"}
style={claimed ? rewardStyle.claimed : (canClaim ? rewardStyle.unclaimed : rewardStyle.disabled)}
size={20} />}
</TouchableOpacity>}
{!isPending && (
<TouchableOpacity
onPress={() => {
if (!claimed) {
this.onClaimPress();
}
}}
>
{claimed && (
<Icon
name={claimed ? 'check-circle' : 'circle'}
style={claimed ? rewardStyle.claimed : canClaim ? rewardStyle.unclaimed : rewardStyle.disabled}
size={20}
/>
)}
</TouchableOpacity>
)}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
</View>
<View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text>
<Text style={rewardStyle.rewardDescription}>{reward.reward_description}</Text>
{claimed && <Link style={rewardStyle.link}
href={`https://explorer.lbry.com/tx/${reward.transaction_id}`}
text={reward.transaction_id.substring(0, 7)}
error={'The transaction URL could not be opened'} />}
{claimed && (
<Link
style={rewardStyle.link}
href={`https://explorer.lbry.com/tx/${reward.transaction_id}`}
text={reward.transaction_id.substring(0, 7)}
error={'The transaction URL could not be opened'}
/>
)}
</View>
<View style={rewardStyle.rightCol}>
<Text style={rewardStyle.rewardAmount}>{reward.reward_amount}</Text>
@ -98,6 +107,6 @@ class RewardCard extends React.PureComponent<Props> {
</TouchableOpacity>
);
}
};
}
export default RewardCard;

View file

@ -7,7 +7,7 @@ import RewardEnrolment from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
user: selectUser(state)
user: selectUser(state),
});
const perform = dispatch => ({
@ -16,4 +16,7 @@ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});
export default connect(select, perform)(RewardEnrolment);
export default connect(
select,
perform
)(RewardEnrolment);

View file

@ -17,12 +17,12 @@ class RewardEnrolment extends React.Component {
const { navigation, setClientSetting } = this.props;
setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true);
navigation.navigate({ routeName: 'DiscoverStack' });
}
};
onEnrollPressed = () => {
const { navigation } = this.props;
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false }});
}
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false } });
};
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
@ -31,20 +31,20 @@ class RewardEnrolment extends React.Component {
<View style={rewardStyle.enrollContainer} onPress>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>
{unclaimedRewardAmount} unclaimed credits
</Text>
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
</View>
<View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}>LBRY credits allow you to purchase content, publish content, and influence the network. You can start earning credits by watching videos on LBRY.</Text>
<Text style={rewardStyle.enrollDescText}>
LBRY credits allow you to purchase content, publish content, and influence the network. You can start
earning credits by watching videos on LBRY.
</Text>
</View>
<View style={rewardStyle.buttonRow}>
<Link style={rewardStyle.notInterestedLink} text={"Not interested"} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={"light"} text={"Enroll"} onPress={this.onEnrollPressed} />
<Link style={rewardStyle.notInterestedLink} text={'Not interested'} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={'light'} text={'Enroll'} onPress={this.onEnrollPressed} />
</View>
</View>
);
}

View file

@ -6,12 +6,15 @@ import RewardSummary from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
user: selectUser(state)
user: selectUser(state),
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data))
notify: data => dispatch(doToast(data)),
});
export default connect(select, perform)(RewardSummary);
export default connect(
select,
perform
)(RewardSummary);

View file

@ -11,7 +11,7 @@ class RewardSummary extends React.Component {
state = {
actionsLeft: 0,
dismissed: false
dismissed: false,
};
componentDidMount() {
@ -42,14 +42,14 @@ class RewardSummary extends React.Component {
this.props.notify({
message: 'You can always claim your rewards from the Rewards page.',
});
}
};
handleSummaryPressed = () => {
const { showVerification } = this.props;
if (showVerification) {
showVerification();
}
}
};
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
@ -58,10 +58,12 @@ class RewardSummary extends React.Component {
return null;
}
if (this.state.dismissed ||
(user && user.is_reward_approved) ||
this.state.actionsLeft === 0 ||
unclaimedRewardAmount === 0) {
if (
this.state.dismissed ||
(user && user.is_reward_approved) ||
this.state.actionsLeft === 0 ||
unclaimedRewardAmount === 0
) {
return null;
}
@ -69,11 +71,9 @@ class RewardSummary extends React.Component {
<TouchableOpacity style={rewardStyle.summaryContainer} onPress={this.handleSummaryPressed}>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>
{unclaimedRewardAmount} unclaimed credits
</Text>
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
</View>
<Button style={rewardStyle.dismissButton} theme={"light"} text={"Dismiss"} onPress={this.onDismissPressed} />
<Button style={rewardStyle.dismissButton} theme={'light'} text={'Dismiss'} onPress={this.onDismissPressed} />
</TouchableOpacity>
);
}

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { NativeModules } from 'react-native';
import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
import SearchInput from './view';
const perform = dispatch => ({
@ -10,7 +10,10 @@ const perform = dispatch => ({
}
return dispatch(doSearch(search));
},
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query, false))
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query, false)),
});
export default connect(null, perform)(SearchInput);
export default connect(
null,
perform
)(SearchInput);

View file

@ -3,11 +3,11 @@ import { TextInput } from 'react-native';
class SearchInput extends React.PureComponent {
static INPUT_TIMEOUT = 500;
state = {
changeTextTimeout: -1
changeTextTimeout: -1,
};
handleChangeText = text => {
clearTimeout(this.state.changeTextTimeout);
if (!text || text.trim().length < 2) {
@ -16,23 +16,24 @@ class SearchInput extends React.PureComponent {
}
const { search, updateSearchQuery } = this.props;
updateSearchQuery(text);
let timeout = setTimeout(() => {
search(text);
}, SearchInput.INPUT_TIMEOUT);
this.setState({ changeTextTimeout: timeout });
}
};
render() {
const { style, value } = this.props;
return (
<TextInput
style={style}
placeholder="Search"
underlineColorAndroid="transparent"
value={value}
onChangeText={text => this.handleChangeText(text)} />
onChangeText={text => this.handleChangeText(text)}
/>
);
}
}

View file

@ -2,9 +2,13 @@ import { connect } from 'react-redux';
import SearchRightHeaderIcon from './view';
import { ACTIONS } from 'lbry-redux';
const perform = dispatch => ({
clearQuery: () => dispatch({
type: ACTIONS.HISTORY_NAVIGATE
})
clearQuery: () =>
dispatch({
type: ACTIONS.HISTORY_NAVIGATE,
}),
});
export default connect(null, perform)(SearchRightHeaderIcon);
export default connect(
null,
perform
)(SearchRightHeaderIcon);

View file

@ -1,14 +1,13 @@
import React from 'react';
import { NavigationActions } from 'react-navigation';
import Feather from "react-native-vector-icons/Feather";
import Feather from 'react-native-vector-icons/Feather';
class SearchRightHeaderIcon extends React.PureComponent {
clearAndGoBack() {
const { navigation } = this.props;
this.props.clearQuery();
navigation.dispatch(NavigationActions.back())
navigation.dispatch(NavigationActions.back());
}
render() {

View file

@ -1,13 +1,6 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import {
ActivityIndicator,
Platform,
Switch,
Text,
TouchableOpacity,
View
} from 'react-native';
import { ActivityIndicator, Platform, Switch, Text, TouchableOpacity, View } from 'react-native';
import { formatBytes } from '../../utils/helper';
import Colors from '../../styles/colors';
import storageStatsStyle from '../../styles/storageStats';
@ -23,15 +16,20 @@ class StorageStatsCard extends React.PureComponent {
totalVideoPercent: 0,
totalOtherBytes: 0,
totalOtherPercent: 0,
showStats: false
showStats: false,
};
componentDidMount() {
// calculate total bytes
const { fileInfos } = this.props;
let totalBytes = 0, totalAudioBytes = 0, totalImageBytes = 0, totalVideoBytes = 0;
let totalAudioPercent = 0, totalImagePercent = 0, totalVideoPercent = 0;
let totalBytes = 0,
totalAudioBytes = 0,
totalImageBytes = 0,
totalVideoBytes = 0;
let totalAudioPercent = 0,
totalImagePercent = 0,
totalVideoPercent = 0;
fileInfos.forEach(fileInfo => {
if (fileInfo.completed) {
@ -59,9 +57,10 @@ class StorageStatsCard extends React.PureComponent {
totalVideoBytes,
totalVideoPercent,
totalOtherBytes: totalBytes - (totalAudioBytes + totalImageBytes + totalVideoBytes),
totalOtherPercent: (100 - (parseFloat(totalAudioPercent) +
parseFloat(totalImagePercent) +
parseFloat(totalVideoPercent))).toFixed(2)
totalOtherPercent: (
100 -
(parseFloat(totalAudioPercent) + parseFloat(totalImagePercent) + parseFloat(totalVideoPercent))
).toFixed(2),
});
}
@ -82,50 +81,52 @@ class StorageStatsCard extends React.PureComponent {
<Switch
style={storageStatsStyle.statsToggle}
value={this.state.showStats}
onValueChange={(value) => this.setState({ showStats: value })} />
onValueChange={value => this.setState({ showStats: value })}
/>
</View>
</View>
{this.state.showStats &&
<View>
<View style={storageStatsStyle.distributionBar}>
<View style={[storageStatsStyle.audioDistribution, { flex: parseFloat(this.state.totalAudioPercent) }]} />
<View style={[storageStatsStyle.imageDistribution, { flex: parseFloat(this.state.totalImagePercent) }]} />
<View style={[storageStatsStyle.videoDistribution, { flex: parseFloat(this.state.totalVideoPercent) }]} />
<View style={[storageStatsStyle.otherDistribution, { flex: parseFloat(this.state.totalOtherPercent) }]} />
{this.state.showStats && (
<View>
<View style={storageStatsStyle.distributionBar}>
<View style={[storageStatsStyle.audioDistribution, { flex: parseFloat(this.state.totalAudioPercent) }]} />
<View style={[storageStatsStyle.imageDistribution, { flex: parseFloat(this.state.totalImagePercent) }]} />
<View style={[storageStatsStyle.videoDistribution, { flex: parseFloat(this.state.totalVideoPercent) }]} />
<View style={[storageStatsStyle.otherDistribution, { flex: parseFloat(this.state.totalOtherPercent) }]} />
</View>
<View style={storageStatsStyle.legend}>
{this.state.totalAudioBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.audioDistribution]} />
<Text style={storageStatsStyle.legendText}>Audio</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalAudioBytes, 2)}</Text>
</View>
)}
{this.state.totalImageBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.imageDistribution]} />
<Text style={storageStatsStyle.legendText}>Images</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalImageBytes, 2)}</Text>
</View>
)}
{this.state.totalVideoBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.videoDistribution]} />
<Text style={storageStatsStyle.legendText}>Videos</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalVideoBytes, 2)}</Text>
</View>
)}
{this.state.totalOtherBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.otherDistribution]} />
<Text style={storageStatsStyle.legendText}>Other</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalOtherBytes, 2)}</Text>
</View>
)}
</View>
</View>
<View style={storageStatsStyle.legend}>
{this.state.totalAudioBytes > 0 &&
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.audioDistribution]} />
<Text style={storageStatsStyle.legendText}>Audio</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalAudioBytes, 2)}</Text>
</View>
}
{this.state.totalImageBytes > 0 &&
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.imageDistribution]} />
<Text style={storageStatsStyle.legendText}>Images</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalImageBytes, 2)}</Text>
</View>
}
{this.state.totalVideoBytes > 0 &&
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.videoDistribution]} />
<Text style={storageStatsStyle.legendText}>Videos</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalVideoBytes, 2)}</Text>
</View>
}
{this.state.totalOtherBytes > 0 &&
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.otherDistribution]} />
<Text style={storageStatsStyle.legendText}>Other</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalOtherBytes, 2)}</Text>
</View>
}
</View>
</View>}
)}
</View>
)
);
}
}

View file

@ -1,10 +1,5 @@
import { connect } from 'react-redux';
import {
doChannelSubscribe,
doChannelUnsubscribe,
selectSubscriptions,
makeSelectIsSubscribed,
} from 'lbryinc';
import { doChannelSubscribe, doChannelUnsubscribe, selectSubscriptions, makeSelectIsSubscribed } from 'lbryinc';
import { doToast } from 'lbry-redux';
import SubscribeButton from './view';

View file

@ -6,14 +6,7 @@ import Colors from 'styles/colors';
class SubscribeButton extends React.PureComponent {
render() {
const {
uri,
isSubscribed,
doChannelSubscribe,
doChannelUnsubscribe,
style,
hideText
} = this.props;
const { uri, isSubscribed, doChannelSubscribe, doChannelUnsubscribe, style, hideText } = this.props;
let styles = [];
if (style) {
@ -32,8 +25,8 @@ class SubscribeButton extends React.PureComponent {
return (
<Button
style={styles}
theme={"light"}
icon={isSubscribed ? "heart-broken" : "heart"}
theme={'light'}
icon={isSubscribed ? 'heart-broken' : 'heart'}
iconColor={iconColor}
solid={isSubscribed ? false : true}
text={hideText ? null : subscriptionLabel}
@ -42,7 +35,8 @@ class SubscribeButton extends React.PureComponent {
channelName: claimName,
uri: normalizeURI(uri),
});
}} />
}}
/>
);
}
}

View file

@ -14,11 +14,11 @@ class SubscribeNotificationButton extends React.PureComponent {
doToast,
enabledChannelNotifications,
isSubscribed,
style
style,
} = this.props;
if (!isSubscribed) {
return null;
return null;
}
let styles = [];
@ -36,8 +36,8 @@ class SubscribeNotificationButton extends React.PureComponent {
return (
<Button
style={styles}
theme={"light"}
icon={shouldNotify ? "bell-slash" : "bell"}
theme={'light'}
icon={shouldNotify ? 'bell-slash' : 'bell'}
solid={true}
onPress={() => {
if (shouldNotify) {
@ -47,7 +47,8 @@ class SubscribeNotificationButton extends React.PureComponent {
doChannelSubscriptionEnableNotifications(name);
doToast({ message: 'You will receive all notifications for new content.' });
}
}} />
}}
/>
);
}
}

View file

@ -14,19 +14,19 @@ class SuggestedSubscriptionItem extends React.PureComponent {
}
}
uriForClaim = (claim) => {
uriForClaim = claim => {
const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = claim;
const uriParams = {};
const uriParams = {};
// This is unfortunate
// https://github.com/lbryio/lbry/issues/1159
const name = claimName || claimNameDownloaded;
uriParams.contentName = name;
uriParams.claimId = claimId;
const uri = buildURI(uriParams);
// This is unfortunate
// https://github.com/lbryio/lbry/issues/1159
const name = claimName || claimNameDownloaded;
uriParams.contentName = name;
uriParams.claimId = claimId;
const uri = buildURI(uriParams);
return uri;
}
return uri;
};
render() {
const { categoryLink, fetching, obscureNsfw, claims, navigation } = this.props;
@ -46,23 +46,26 @@ class SuggestedSubscriptionItem extends React.PureComponent {
style={subscriptionsStyle.compactMainFileItem}
mediaStyle={subscriptionsStyle.fileItemMedia}
uri={this.uriForClaim(claims[0])}
navigation={navigation} />
{(claims.length > 1) &&
<FlatList style={subscriptionsStyle.compactItems}
horizontal={true}
renderItem={ ({item}) => (
navigation={navigation}
/>
{claims.length > 1 && (
<FlatList
style={subscriptionsStyle.compactItems}
horizontal={true}
renderItem={({ item }) => (
<FileItem
style={subscriptionsStyle.compactFileItem}
mediaStyle={subscriptionsStyle.compactFileItemMedia}
key={item}
uri={normalizeURI(item)}
navigation={navigation}
compactView={true} />
)
}
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
keyExtractor={(item, index) => item}
/>}
compactView={true}
/>
)}
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
keyExtractor={(item, index) => item}
/>
)}
</View>
);
}

View file

@ -10,4 +10,4 @@ const select = state => ({
export default connect(
select,
null
)(SuggestedSubscriptions);
)(SuggestedSubscriptions);

View file

@ -22,34 +22,33 @@ class SuggestedSubscriptions extends React.PureComponent {
}
return suggested ? (
<SectionList style={subscriptionsStyle.scrollContainer}
renderItem={ ({item, index, section}) => (
<SuggestedSubscriptionItem
key={item}
categoryLink={normalizeURI(item)}
navigation={navigation} />
)
}
renderSectionHeader={
({section: {title}}) => {
const titleParts = title.split(';');
const channelName = titleParts[0];
const channelUri = normalizeURI(titleParts[1]);
return (
<View style={subscriptionsStyle.titleRow}>
<Link style={subscriptionsStyle.channelTitle} text={channelName} onPress={() => {
<SectionList
style={subscriptionsStyle.scrollContainer}
renderItem={({ item, index, section }) => (
<SuggestedSubscriptionItem key={item} categoryLink={normalizeURI(item)} navigation={navigation} />
)}
renderSectionHeader={({ section: { title } }) => {
const titleParts = title.split(';');
const channelName = titleParts[0];
const channelUri = normalizeURI(titleParts[1]);
return (
<View style={subscriptionsStyle.titleRow}>
<Link
style={subscriptionsStyle.channelTitle}
text={channelName}
onPress={() => {
navigateToUri(navigation, normalizeURI(channelUri));
}} />
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
</View>
)
}
}
sections={suggested.map(({ uri, label }) => ({ title: (label + ';' + uri), data: [uri] }))}
}}
/>
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
</View>
);
}}
sections={suggested.map(({ uri, label }) => ({ title: label + ';' + uri, data: [uri] }))}
keyExtractor={(item, index) => item}
/>
) : null;
}
}
export default SuggestedSubscriptions;
export default SuggestedSubscriptions;

View file

@ -8,4 +8,7 @@ const select = state => ({
myClaims: selectAllMyClaimsByOutpoint(state),
});
export default connect(select, null)(TransactionList);
export default connect(
select,
null
)(TransactionList);

View file

@ -25,20 +25,25 @@ class TransactionListItem extends React.PureComponent {
<Link
style={transactionListStyle.link}
onPress={() => navigateToUri(navigation, buildURI({ claimName: name, claimId }))}
text={name} />
text={name}
/>
)}
</View>
<View style={transactionListStyle.col}>
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>{formatCredits(amount, 8)}</Text>
{ fee !== 0 && (<Text style={[transactionListStyle.amount, transactionListStyle.text]}>fee {formatCredits(fee, 8)}</Text>) }
{fee !== 0 && (
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>fee {formatCredits(fee, 8)}</Text>
)}
</View>
</View>
<View style={transactionListStyle.row}>
<View style={transactionListStyle.col}>
<Link style={transactionListStyle.smallLink}
text={txid.substring(0, 8)}
href={`https://explorer.lbry.com/tx/${txid}`}
error={'The transaction URL could not be opened'} />
<Link
style={transactionListStyle.smallLink}
text={txid.substring(0, 8)}
href={`https://explorer.lbry.com/tx/${txid}`}
error={'The transaction URL could not be opened'}
/>
</View>
<View style={transactionListStyle.col}>
{date ? (

View file

@ -38,18 +38,18 @@ class TransactionList extends React.PureComponent {
return filter === 'all' || filter === transaction.type;
}
render() {
const { emptyMessage, rewards, transactions, navigation } = this.props;
const { filter } = this.state;
const transactionList = transactions.filter(this.filterTransaction);
return (
<View>
{!transactionList.length && (
<Text style={transactionListStyle.noTransactions}>{emptyMessage || 'No transactions to list.'}</Text>
)}
{!!transactionList.length && (
<View>
{transactionList.map(t => (

View file

@ -17,4 +17,7 @@ const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionListRecent);
export default connect(
select,
perform
)(TransactionListRecent);

View file

@ -27,14 +27,9 @@ class TransactionListRecent extends React.PureComponent<Props> {
<View style={walletStyle.transactionsCard}>
<View style={[walletStyle.row, walletStyle.transactionsHeader]}>
<Text style={walletStyle.transactionsTitle}>Recent Transactions</Text>
<Link style={walletStyle.link}
navigation={navigation}
text={'View All'}
href={'#TransactionHistory'} />
<Link style={walletStyle.link} navigation={navigation} text={'View All'} href={'#TransactionHistory'} />
</View>
{fetchingTransactions && (
<Text style={walletStyle.infoText}>Fetching transactions...</Text>
)}
{fetchingTransactions && <Text style={walletStyle.infoText}>Fetching transactions...</Text>}
{!fetchingTransactions && (
<TransactionList
navigation={navigation}

View file

@ -3,7 +3,7 @@ import {
doUpdateSearchQuery,
selectSearchState as selectSearch,
selectSearchValue,
selectSearchSuggestions
selectSearchSuggestions,
} from 'lbry-redux';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import UriBar from './view';
@ -15,7 +15,7 @@ const select = state => {
...searchState,
query: selectSearchValue(state),
currentRoute: selectCurrentRoute(state),
suggestions: selectSearchSuggestions(state)
suggestions: selectSearchSuggestions(state),
};
};
@ -23,4 +23,7 @@ const perform = dispatch => ({
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
});
export default connect(select, perform)(UriBar);
export default connect(
select,
perform
)(UriBar);

View file

@ -13,16 +13,16 @@ class UriBarItem extends React.PureComponent {
let icon;
switch (type) {
case SEARCH_TYPES.CHANNEL:
icon = <Icon name="at" size={18} />
icon = <Icon name="at" size={18} />;
break;
case SEARCH_TYPES.SEARCH:
icon = <Icon name="search" size={18} />
icon = <Icon name="search" size={18} />;
break;
case SEARCH_TYPES.FILE:
default:
icon = <Icon name="file" size={18} />
icon = <Icon name="file" size={18} />;
break;
}
@ -30,7 +30,9 @@ class UriBarItem extends React.PureComponent {
<TouchableOpacity style={uriBarStyle.item} onPress={onPress}>
{icon}
<View style={uriBarStyle.itemContent}>
<Text style={uriBarStyle.itemText} numberOfLines={1}>{shorthand || value} - {type === SEARCH_TYPES.SEARCH ? 'Search' : value}</Text>
<Text style={uriBarStyle.itemText} numberOfLines={1}>
{shorthand || value} - {type === SEARCH_TYPES.SEARCH ? 'Search' : value}
</Text>
<Text style={uriBarStyle.itemDesc} numberOfLines={1}>
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
@ -38,7 +40,7 @@ class UriBarItem extends React.PureComponent {
</Text>
</View>
</TouchableOpacity>
)
);
}
}

View file

@ -16,7 +16,7 @@ class UriBar extends React.PureComponent {
keyboardDidHideListener = null;
componentDidMount () {
componentDidMount() {
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
this.setSelection();
}
@ -44,7 +44,7 @@ class UriBar extends React.PureComponent {
inputText: null,
focused: false,
// TODO: Add a setting to enable / disable direct search?
directSearch: true
directSearch: true,
};
}
@ -66,15 +66,14 @@ class UriBar extends React.PureComponent {
if (onSearchSubmitted) {
onSearchSubmitted(text);
} else {
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text }});
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
}
}
}, UriBar.INPUT_TIMEOUT);
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
}
};
handleItemPress = (item) => {
handleItemPress = item => {
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
const { type, value } = item;
@ -89,23 +88,23 @@ class UriBar extends React.PureComponent {
return;
}
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value } });
} else {
const uri = normalizeURI(value);
navigateToUri(navigation, uri);
}
}
};
_keyboardDidHide = () => {
if (this.textInput) {
this.textInput.blur();
}
this.setState({ focused: false });
}
};
setSelection() {
if (this.textInput) {
this.textInput.setNativeProps({ selection: { start: 0, end: 0 }});
this.textInput.setNativeProps({ selection: { start: 0, end: 0 } });
}
}
@ -127,17 +126,17 @@ class UriBar extends React.PureComponent {
}
// Open the search page with the query populated
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText }});
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText } });
}
}
}
};
onSearchPageBlurred() {
this.setState({ currenValueSet: false });
}
render() {
const { navigation, suggestions, query, value } = this.props;
const { navigation, suggestions, query, value } = this.props;
if (this.state.currentValue === null) {
this.setState({ currentValue: value });
}
@ -155,39 +154,46 @@ class UriBar extends React.PureComponent {
size={24}
style={uriBarStyle.drawerMenuButton}
iconStyle={discoverStyle.drawerHamburger}
onPress={() => navigation.openDrawer() } />
<TextInput ref={(ref) => { this.textInput = ref }}
style={uriBarStyle.uriText}
onLayout={() => { this.setSelection(); }}
selectTextOnFocus={true}
placeholder={'Search movies, music, and more'}
underlineColorAndroid={'transparent'}
numberOfLines={1}
clearButtonMode={'while-editing'}
value={this.state.currentValue}
returnKeyType={'go'}
inlineImageLeft={'baseline_search_black_24'}
inlineImagePadding={16}
onFocus={() => this.setState({ focused: true })}
onBlur={() => {
this.setState({ focused: false });
this.setSelection();
}}
onChangeText={this.handleChangeText}
onSubmitEditing={this.handleSubmitEditing}/>
{(this.state.focused && !this.state.directSearch) && (
<View style={uriBarStyle.suggestions}>
<FlatList style={uriBarStyle.suggestionList}
data={suggestions}
keyboardShouldPersistTaps={'handled'}
keyExtractor={(item, value) => item.value}
renderItem={({item}) => (
<UriBarItem
item={item}
navigation={navigation}
onPress={() => this.handleItemPress(item)}
/>)} />
</View>)}
onPress={() => navigation.openDrawer()}
/>
<TextInput
ref={ref => {
this.textInput = ref;
}}
style={uriBarStyle.uriText}
onLayout={() => {
this.setSelection();
}}
selectTextOnFocus={true}
placeholder={'Search movies, music, and more'}
underlineColorAndroid={'transparent'}
numberOfLines={1}
clearButtonMode={'while-editing'}
value={this.state.currentValue}
returnKeyType={'go'}
inlineImageLeft={'baseline_search_black_24'}
inlineImagePadding={16}
onFocus={() => this.setState({ focused: true })}
onBlur={() => {
this.setState({ focused: false });
this.setSelection();
}}
onChangeText={this.handleChangeText}
onSubmitEditing={this.handleSubmitEditing}
/>
{this.state.focused && !this.state.directSearch && (
<View style={uriBarStyle.suggestions}>
<FlatList
style={uriBarStyle.suggestionList}
data={suggestions}
keyboardShouldPersistTaps={'handled'}
keyExtractor={(item, value) => item.value}
renderItem={({ item }) => (
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
)}
/>
</View>
)}
</View>
</View>
);

View file

@ -1,10 +1,5 @@
import { connect } from 'react-redux';
import {
doCheckAddressIsMine,
doGetNewAddress,
selectReceiveAddress,
selectGettingNewAddress,
} from 'lbry-redux';
import { doCheckAddressIsMine, doGetNewAddress, selectReceiveAddress, selectGettingNewAddress } from 'lbry-redux';
import WalletAddress from './view';
const select = state => ({
@ -17,4 +12,7 @@ const perform = dispatch => ({
getNewAddress: () => dispatch(doGetNewAddress()),
});
export default connect(select, perform)(WalletAddress);
export default connect(
select,
perform
)(WalletAddress);

View file

@ -28,16 +28,20 @@ class WalletAddress extends React.PureComponent<Props> {
return (
<View style={walletStyle.card}>
<Text style={walletStyle.title}>Receive Credits</Text>
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>Use this wallet address to receive credits sent by another user (or yourself).</Text>
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>
Use this wallet address to receive credits sent by another user (or yourself).
</Text>
<Address address={receiveAddress} style={walletStyle.bottomMarginSmall} />
<Button style={[walletStyle.button, walletStyle.bottomMarginLarge]}
icon={'sync'}
text={'Get new address'}
onPress={getNewAddress}
disabled={gettingNewAddress}
/>
<Button
style={[walletStyle.button, walletStyle.bottomMarginLarge]}
icon={'sync'}
text={'Get new address'}
onPress={getNewAddress}
disabled={gettingNewAddress}
/>
<Text style={walletStyle.smallText}>
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple
addresses can be helpful for keeping track of incoming payments from multiple sources.
</Text>
</View>
);

View file

@ -6,4 +6,7 @@ const select = state => ({
balance: selectBalance(state),
});
export default connect(select, null)(WalletBalance);
export default connect(
select,
null
)(WalletBalance);

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { Image, Text, View } from 'react-native';
import { Lbry, formatCredits } from 'lbry-redux'
import { Lbry, formatCredits } from 'lbry-redux';
import Address from 'component/address';
import Button from 'component/button';
import walletStyle from 'styles/wallet';
@ -19,7 +19,7 @@ class WalletBalance extends React.PureComponent<Props> {
<Text style={walletStyle.balanceTitle}>Balance</Text>
<Text style={walletStyle.balanceCaption}>You currently have</Text>
<Text style={walletStyle.balance}>
{(balance || balance === 0) && (formatCredits(parseFloat(balance), 2) + ' LBC')}
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
</Text>
</View>
);

View file

@ -4,7 +4,7 @@ import {
doSendDraftTransaction,
selectDraftTransaction,
selectDraftTransactionError,
selectBalance
selectBalance,
} from 'lbry-redux';
import WalletSend from './view';
@ -16,7 +16,10 @@ const select = state => ({
const perform = dispatch => ({
sendToAddress: (address, amount) => dispatch(doSendDraftTransaction(address, amount)),
notify: (data) => dispatch(doToast(data))
notify: data => dispatch(doToast(data)),
});
export default connect(select, perform)(WalletSend);
export default connect(
select,
perform
)(WalletSend);

View file

@ -22,7 +22,7 @@ class WalletSend extends React.PureComponent<Props> {
amount: null,
address: null,
addressChanged: false,
addressValid: false
addressValid: false,
};
componentWillUpdate(nextProps) {
@ -51,18 +51,18 @@ class WalletSend extends React.PureComponent<Props> {
if (amount && address) {
// Show confirmation before send
Alert.alert(
'Send LBC',
`Are you sure you want to send ${amount} LBC to ${address}?`,
[
Alert.alert('Send LBC', `Are you sure you want to send ${amount} LBC to ${address}?`, [
{ text: 'No' },
{ text: 'Yes', onPress: () => {
sendToAddress(address, parseFloat(amount));
this.setState({ address: null, amount: null });
}}
{
text: 'Yes',
onPress: () => {
sendToAddress(address, parseFloat(amount));
this.setState({ address: null, amount: null });
},
},
]);
}
}
};
handleAddressInputBlur = () => {
if (this.state.addressChanged && !this.state.addressValid) {
@ -71,52 +71,58 @@ class WalletSend extends React.PureComponent<Props> {
message: 'The recipient address is not a valid LBRY address.',
});
}
}
};
handleAddressInputSubmit = () => {
if (this.amountInput) {
this.amountInput.focus();
}
}
};
render() {
const { balance } = this.props;
const canSend = this.state.address &&
this.state.amount > 0 &&
this.state.address.trim().length > 0;
const canSend = this.state.address && this.state.amount > 0 && this.state.address.trim().length > 0;
return (
<View style={walletStyle.card}>
<Text style={walletStyle.title}>Send Credits</Text>
<Text style={walletStyle.text}>Recipient address</Text>
<View style={[walletStyle.row, walletStyle.bottomMarginMedium]}>
<TextInput onChangeText={value => this.setState({
address: value,
addressChanged: true,
addressValid: (value.trim().length == 0 || regexAddress.test(value))
})}
onBlur={this.handleAddressInputBlur}
onSubmitEditing={this.handleAddressInputSubmit}
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
value={this.state.address}
returnKeyType={'next'}
style={[walletStyle.input, walletStyle.addressInput, walletStyle.bottomMarginMedium]} />
<TextInput
onChangeText={value =>
this.setState({
address: value,
addressChanged: true,
addressValid: value.trim().length == 0 || regexAddress.test(value),
})
}
onBlur={this.handleAddressInputBlur}
onSubmitEditing={this.handleAddressInputSubmit}
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
value={this.state.address}
returnKeyType={'next'}
style={[walletStyle.input, walletStyle.addressInput, walletStyle.bottomMarginMedium]}
/>
</View>
<Text style={walletStyle.text}>Amount</Text>
<View style={walletStyle.row}>
<View style={walletStyle.amountRow}>
<TextInput ref={ref => this.amountInput = ref}
onChangeText={value => this.setState({amount: value})}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.amount}
style={[walletStyle.input, walletStyle.amountInput]} />
<TextInput
ref={ref => (this.amountInput = ref)}
onChangeText={value => this.setState({ amount: value })}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.amount}
style={[walletStyle.input, walletStyle.amountInput]}
/>
<Text style={[walletStyle.text, walletStyle.currency]}>LBC</Text>
</View>
<Button text={'Send'}
style={[walletStyle.button, walletStyle.sendButton]}
disabled={!canSend}
onPress={this.handleSend} />
<Button
text={'Send'}
style={[walletStyle.button, walletStyle.sendButton]}
disabled={!canSend}
onPress={this.handleSend}
/>
</View>
</View>
);

View file

@ -7,4 +7,7 @@ const select = state => ({
deviceWalletSynced: makeSelectClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED)(state),
});
export default connect(select, null)(WalletSyncDriver);
export default connect(
select,
null
)(WalletSyncDriver);

View file

@ -8,7 +8,7 @@ class WalletSyncDriver extends React.PureComponent<Props> {
onEnableSyncPressed = () => {
const { navigation } = this.props;
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: true } });
}
};
render() {
const { deviceWalletSynced } = this.props;
@ -17,14 +17,29 @@ class WalletSyncDriver extends React.PureComponent<Props> {
<View style={walletStyle.syncDriverCard}>
<View style={walletStyle.syncDriverRow}>
<Text style={walletStyle.syncDriverTitle}>Wallet sync is {deviceWalletSynced ? 'on' : 'off'}.</Text>
{!deviceWalletSynced &&
<Link text="Sync FAQ" href="https://lbry.com/faq/how-to-backup-wallet#sync" style={walletStyle.syncDriverText} />}
{!deviceWalletSynced && (
<Link
text="Sync FAQ"
href="https://lbry.com/faq/how-to-backup-wallet#sync"
style={walletStyle.syncDriverText}
/>
)}
</View>
{!deviceWalletSynced &&
<View style={walletStyle.actionRow}>
<Button style={walletStyle.enrollButton} theme={"light"} text={"Enable"} onPress={this.onEnableSyncPressed} />
<Link text="Manual backup" href="https://lbry.com/faq/how-to-backup-wallet#android" style={walletStyle.syncDriverText} />
</View>}
{!deviceWalletSynced && (
<View style={walletStyle.actionRow}>
<Button
style={walletStyle.enrollButton}
theme={'light'}
text={'Enable'}
onPress={this.onEnableSyncPressed}
/>
<Link
text="Manual backup"
href="https://lbry.com/faq/how-to-backup-wallet#android"
style={walletStyle.syncDriverText}
/>
</View>
)}
</View>
);
}

View file

@ -1,73 +1,72 @@
const Constants = {
FIRST_RUN_PAGE_WELCOME: "welcome",
FIRST_RUN_PAGE_EMAIL_COLLECT: "email-collect",
FIRST_RUN_PAGE_EMAIL_VERIFY:"email-verify",
FIRST_RUN_PAGE_WALLET: "wallet",
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account",
FIRST_RUN_PAGE_WELCOME: 'welcome',
FIRST_RUN_PAGE_EMAIL_COLLECT: 'email-collect',
FIRST_RUN_PAGE_EMAIL_VERIFY: 'email-verify',
FIRST_RUN_PAGE_WALLET: 'wallet',
FIRST_RUN_PAGE_SKIP_ACCOUNT: 'skip-account',
VERIFY_PAGE_EMAIL: "email-verify",
VERIFY_PAGE_PHONE_NUMBER: "phone-number-verify",
VERIFY_PAGE_EMAIL: 'email-verify',
VERIFY_PAGE_PHONE_NUMBER: 'phone-number-verify',
PHASE_COLLECTION: "collection",
PHASE_VERIFICATION: "verification",
PHASE_COLLECTION: 'collection',
PHASE_VERIFICATION: 'verification',
CONTENT_TAB: "content",
ABOUT_TAB: "about",
CONTENT_TAB: 'content',
ABOUT_TAB: 'about',
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
KEY_FIRST_USER_AUTH: "firstUserAuth",
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
KEY_FIRST_RUN_EMAIL: 'firstRunEmail',
KEY_FIRST_RUN_PASSWORD: 'firstRunPassword',
KEY_FIRST_USER_AUTH: 'firstUserAuth',
KEY_SHOULD_VERIFY_EMAIL: 'shouldVerifyEmail',
KEY_EMAIL_VERIFY_PENDING: 'emailVerifyPending',
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks",
SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode",
SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown",
SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled",
SETTING_BACKUP_DISMISSED: "backupDismissed",
SETTING_REWARDS_NOT_INTERESTED: "rewardsNotInterested",
SETTING_DEVICE_WALLET_SYNCED: "deviceWalletSynced",
SETTING_ALPHA_UNDERSTANDS_RISKS: 'alphaUnderstandRisks',
SETTING_SUBSCRIPTIONS_VIEW_MODE: 'subscriptionsViewMode',
SETTING_RATING_REMINDER_LAST_SHOWN: 'ratingReminderLastShown',
SETTING_RATING_REMINDER_DISABLED: 'ratingReminderDisabled',
SETTING_BACKUP_DISMISSED: 'backupDismissed',
SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS",
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
ACTION_PUSH_DRAWER_STACK: "PUSH_DRAWER_STACK",
ACTION_POP_DRAWER_STACK: "POP_DRAWER_STACK",
ACTION_SET_PLAYER_VISIBLE: "SET_PLAYER_VISIBLE",
ACTION_PUSH_DRAWER_STACK: 'PUSH_DRAWER_STACK',
ACTION_POP_DRAWER_STACK: 'POP_DRAWER_STACK',
ACTION_SET_PLAYER_VISIBLE: 'SET_PLAYER_VISIBLE',
ACTION_REACT_NAVIGATION_RESET: "Navigation/RESET",
ACTION_REACT_NAVIGATION_NAVIGATE: "Navigation/NAVIGATE",
ACTION_REACT_NAVIGATION_REPLACE: "Navigation/REPLACE",
ACTION_REACT_NAVIGATION_RESET: 'Navigation/RESET',
ACTION_REACT_NAVIGATION_NAVIGATE: 'Navigation/NAVIGATE',
ACTION_REACT_NAVIGATION_REPLACE: 'Navigation/REPLACE',
PAGE_REWARDS: "rewards",
PAGE_SETTINGS: "settings",
PAGE_TRENDING: "trending",
PAGE_WALLET: "wallet",
PAGE_REWARDS: 'rewards',
PAGE_SETTINGS: 'settings',
PAGE_TRENDING: 'trending',
PAGE_WALLET: 'wallet',
DRAWER_ROUTE_DISCOVER: "Discover",
DRAWER_ROUTE_TRENDING: "Trending",
DRAWER_ROUTE_SUBSCRIPTIONS: "Subscriptions",
DRAWER_ROUTE_MY_LBRY: "Downloads",
DRAWER_ROUTE_PUBLISH: "Publish",
DRAWER_ROUTE_REWARDS: "Rewards",
DRAWER_ROUTE_WALLET: "Wallet",
DRAWER_ROUTE_SETTINGS: "Settings",
DRAWER_ROUTE_ABOUT: "About",
DRAWER_ROUTE_SEARCH: "Search",
DRAWER_ROUTE_TRANSACTION_HISTORY: "TransactionHistory",
DRAWER_ROUTE_DISCOVER: 'Discover',
DRAWER_ROUTE_TRENDING: 'Trending',
DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
DRAWER_ROUTE_MY_LBRY: 'Downloads',
DRAWER_ROUTE_REWARDS: 'Rewards',
DRAWER_ROUTE_WALLET: 'Wallet',
DRAWER_ROUTE_SETTINGS: 'Settings',
DRAWER_ROUTE_ABOUT: 'About',
DRAWER_ROUTE_SEARCH: 'Search',
DRAWER_ROUTE_TRANSACTION_HISTORY: 'TransactionHistory',
FULL_ROUTE_NAME_DISCOVER: "DiscoverStack",
FULL_ROUTE_NAME_TRENDING: "TrendingStack",
FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: "MySubscriptionsStack",
FULL_ROUTE_NAME_WALLET: "WalletStack",
FULL_ROUTE_NAME_MY_LBRY: "MyLBRYStack",
FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
FULL_ROUTE_NAME_TRENDING: 'TrendingStack',
FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: 'MySubscriptionsStack',
FULL_ROUTE_NAME_WALLET: 'WalletStack',
FULL_ROUTE_NAME_MY_LBRY: 'MyLBRYStack',
ROUTE_FILE: "File",
ROUTE_FILE: 'File',
SUBSCRIPTIONS_VIEW_ALL: "view_all",
SUBSCRIPTIONS_VIEW_LATEST_FIRST: "view_latest_first",
SUBSCRIPTIONS_VIEW_ALL: 'view_all',
SUBSCRIPTIONS_VIEW_LATEST_FIRST: 'view_latest_first',
PLAY_STORE_URL: "https://play.google.com/store/apps/details?id=io.lbry.browser",
PLAY_STORE_URL: 'https://play.google.com/store/apps/details?id=io.lbry.browser',
RATING_REMINDER_INTERVAL: 604800, // 7 days (7 * 24 * 3600s)
};
@ -83,5 +82,5 @@ export const DrawerRoutes = [
Constants.DRAWER_ROUTE_SETTINGS,
Constants.DRAWER_ROUTE_ABOUT,
Constants.DRAWER_ROUTE_SEARCH,
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY,
];

View file

@ -1,12 +1,7 @@
import React from 'react';
import { setJSExceptionHandler } from 'react-native-exception-handler';
import { Provider, connect } from 'react-redux';
import {
AppRegistry,
Text,
View,
NativeModules
} from 'react-native';
import { AppRegistry, Text, View, NativeModules } from 'react-native';
import {
Lbry,
claimsReducer,
@ -15,7 +10,7 @@ import {
fileInfoReducer,
notificationsReducer,
searchReducer,
walletReducer
walletReducer,
} from 'lbry-redux';
import {
authReducer,
@ -25,10 +20,14 @@ import {
rewardsReducer,
subscriptionsReducer,
syncReducer,
userReducer
userReducer,
} from 'lbryinc';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import AppWithNavigationState, { AppNavigator, navigatorReducer, reactNavigationMiddleware } from 'component/AppNavigator';
import AppWithNavigationState, {
AppNavigator,
navigatorReducer,
reactNavigationMiddleware,
} from 'component/AppNavigator';
import { persistStore, autoRehydrate } from 'redux-persist';
import AsyncStorage from '@react-native-community/async-storage';
import FilesystemStorage from 'redux-persist-filesystem-storage';
@ -39,10 +38,9 @@ import drawerReducer from 'redux/reducers/drawer';
import settingsReducer from 'redux/reducers/settings';
import thunk from 'redux-thunk';
kauffj commented 2019-07-02 16:35:06 +02:00 (Migrated from github.com)
Review

Didn't prettier/linter get added?

Didn't prettier/linter get added?
akinwale commented 2019-07-03 11:47:22 +02:00 (Migrated from github.com)
Review

It did. But it looks like the precommit script doesn't fire up if I run the commit from the top-level folder. I'll look into fixing this.

It did. But it looks like the precommit script doesn't fire up if I run the commit from the top-level folder. I'll look into fixing this.
const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) {
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));
}
};
setJSExceptionHandler(globalExceptionHandler, true);
@ -97,7 +95,7 @@ const reducers = combineReducers({
subscriptions: subscriptionsReducer,
sync: syncReducer,
user: userReducer,
wallet: walletReducer
wallet: walletReducer,
});
const bulkThunk = createBulkThunkMiddleware();
@ -109,10 +107,7 @@ const composeEnhancers = compose;
const store = createStore(
enableBatching(reducers),
{}, // initial state,
composeEnhancers(
autoRehydrate(),
applyMiddleware(...middleware)
)
composeEnhancers(autoRehydrate(), applyMiddleware(...middleware))
);
window.store = store;
@ -130,7 +125,7 @@ const persistOptions = {
// read the data
transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
debounce: 10000,
storage: FilesystemStorage
storage: FilesystemStorage,
};
persistStore(store, persistOptions, err => {
@ -140,7 +135,7 @@ persistStore(store, persistOptions, err => {
});
// TODO: Find i18n module that is compatible with react-native
global.__ = (str) => str;
global.__ = str => str;
class LBRYApp extends React.Component {
render() {

View file

@ -21,4 +21,7 @@ const perform = dispatch => ({
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(select, perform)(AboutPage);
export default connect(
select,
perform
)(AboutPage);

View file

@ -11,7 +11,7 @@ class AboutPage extends React.PureComponent {
state = {
appVersion: null,
lbryId: null,
versionInfo: null
versionInfo: null,
};
didFocusListener;
@ -46,7 +46,7 @@ class AboutPage extends React.PureComponent {
if (NativeModules.VersionInfo) {
NativeModules.VersionInfo.getAppVersion().then(version => {
this.setState({appVersion: version});
this.setState({ appVersion: version });
});
}
Lbry.version().then(info => {
@ -61,7 +61,7 @@ class AboutPage extends React.PureComponent {
});
if (!this.props.accessToken) this.props.fetchAccessToken();
}
};
render() {
const { accessToken, drawerStack, navigation, notify, popDrawerStack, userEmail } = this.props;
@ -70,15 +70,14 @@ class AboutPage extends React.PureComponent {
return (
<View style={aboutStyle.container}>
<PageHeader title={"About LBRY"}
onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
<PageHeader title={'About LBRY'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
<ScrollView style={aboutStyle.scrollContainer}>
<Text style={aboutStyle.title}>Content Freedom</Text>
<Text style={aboutStyle.paragraph}>
LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer
content distribution platform for creators to upload and share content, and earn LBRY credits
for their effort. Users will be able to find a wide selection of videos, music, ebooks and other
digital content they are interested in.
LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content
distribution platform for creators to upload and share content, and earn LBRY credits for their effort.
Users will be able to find a wide selection of videos, music, ebooks and other digital content they are
interested in.
</Text>
<View style={aboutStyle.links}>
<Link style={aboutStyle.link} href="https://lbry.com/faq/what-is-lbry" text="What is LBRY?" />
@ -87,7 +86,8 @@ class AboutPage extends React.PureComponent {
</View>
<Text style={aboutStyle.socialTitle}>Get Social</Text>
<Text style={aboutStyle.paragraph}>
You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit.
You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or
Reddit.
</Text>
<View style={aboutStyle.links}>
<Link style={aboutStyle.link} href="https://discordapp.com/invite/Z3bERWA" text="Discord" />
@ -98,54 +98,88 @@ class AboutPage extends React.PureComponent {
<Link style={aboutStyle.link} href="https://t.me/lbryofficial" text="Telegram" />
</View>
<Text style={aboutStyle.releaseInfoTitle}>App info</Text>
{userEmail && userEmail.trim().length > 0 &&
<View style={aboutStyle.verticalRow}>
<View style={aboutStyle.innerRow}>
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Connected Email</Text></View>
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{userEmail}</Text></View>
{userEmail && userEmail.trim().length > 0 && (
<View style={aboutStyle.verticalRow}>
<View style={aboutStyle.innerRow}>
<View style={aboutStyle.col}>
<Text style={aboutStyle.text}>Connected Email</Text>
</View>
<View style={aboutStyle.col}>
<Text selectable={true} style={aboutStyle.valueText}>
{userEmail}
</Text>
</View>
</View>
<View>
<Link
style={aboutStyle.listLink}
href={`http://lbry.com/list/edit/${accessToken}`}
text="Update mailing preferences"
/>
</View>
</View>
<View>
<Link
style={aboutStyle.listLink}
href={`http://lbry.com/list/edit/${accessToken}`}
text="Update mailing preferences" />
</View>
</View>}
)}
<View style={aboutStyle.row}>
<View style={aboutStyle.col}><Text style={aboutStyle.text}>App version</Text></View>
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{this.state.appVersion}</Text></View>
<View style={aboutStyle.col}>
<Text style={aboutStyle.text}>App version</Text>
</View>
<View style={aboutStyle.col}>
<Text selectable={true} style={aboutStyle.valueText}>
{this.state.appVersion}
</Text>
</View>
</View>
<View style={aboutStyle.row}>
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Daemon (lbrynet)</Text></View>
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{ver ? ver.lbrynet_version : loading }</Text></View>
<View style={aboutStyle.col}>
<Text style={aboutStyle.text}>Daemon (lbrynet)</Text>
</View>
<View style={aboutStyle.col}>
<Text selectable={true} style={aboutStyle.valueText}>
{ver ? ver.lbrynet_version : loading}
</Text>
</View>
</View>
<View style={aboutStyle.row}>
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Platform</Text></View>
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{ver ? ver.platform : loading }</Text></View>
<View style={aboutStyle.col}>
<Text style={aboutStyle.text}>Platform</Text>
</View>
<View style={aboutStyle.col}>
<Text selectable={true} style={aboutStyle.valueText}>
{ver ? ver.platform : loading}
</Text>
</View>
</View>
<View style={aboutStyle.row}>
<View style={aboutStyle.col}>
<Text style={aboutStyle.text}>Installation ID</Text>
<Text selectable={true} style={aboutStyle.lineValueText}>{this.state.lbryId ? this.state.lbryId : loading}</Text>
<Text selectable={true} style={aboutStyle.lineValueText}>
{this.state.lbryId ? this.state.lbryId : loading}
</Text>
</View>
</View>
<View style={aboutStyle.row}>
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Logs</Text></View>
<View style={aboutStyle.col}>
<Link style={aboutStyle.listLink} text="Send log" onPress={() => {
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.shareLogFile((error) => {
if (error) {
notify(error);
}
});
}
}} />
<Text style={aboutStyle.text}>Logs</Text>
</View>
<View style={aboutStyle.col}>
<Link
style={aboutStyle.listLink}
text="Send log"
onPress={() => {
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.shareLogFile(error => {
if (error) {
notify(error);
}
});
}
}}
/>
</View>
</View>
</ScrollView>

View file

@ -4,7 +4,7 @@ import {
makeSelectClaimForUri,
makeSelectClaimsInChannelForCurrentPageState,
makeSelectFetchingChannelClaims,
makeSelectTotalPagesForChannel
makeSelectTotalPagesForChannel,
} from 'lbry-redux';
import { doPopDrawerStack } from 'redux/actions/drawer';
import { selectDrawerStack } from 'redux/selectors/drawer';
@ -20,7 +20,10 @@ const select = (state, props) => ({
const perform = dispatch => ({
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
popDrawerStack: () => dispatch(doPopDrawerStack())
popDrawerStack: () => dispatch(doPopDrawerStack()),
});
export default connect(select, perform)(ChannelPage);
export default connect(
select,
perform
)(ChannelPage);

View file

@ -1,14 +1,6 @@
// @flow
import React from 'react';
import {
ActivityIndicator,
Dimensions,
Image,
ScrollView,
Text,
TouchableOpacity,
View
} from 'react-native';
import { ActivityIndicator, Dimensions, Image, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
import { navigateBack } from 'utils/helper';
import Colors from 'styles/colors';
@ -25,7 +17,7 @@ class ChannelPage extends React.PureComponent {
state = {
page: 1,
showPageButtons: false,
activeTab: Constants.CONTENT_TAB
activeTab: Constants.CONTENT_TAB,
};
componentDidMount() {
@ -43,7 +35,7 @@ class ChannelPage extends React.PureComponent {
fetchClaims(uri, this.state.page);
});
}
}
};
handleNextPage = () => {
const { uri, fetchClaims, totalPages } = this.props;
@ -52,7 +44,7 @@ class ChannelPage extends React.PureComponent {
fetchClaims(uri, this.state.page);
});
}
}
};
renderContent = () => {
const { fetching, claimsInChannel, totalPages, navigation } = this.props;
@ -68,13 +60,15 @@ class ChannelPage extends React.PureComponent {
} else {
contentList =
claimsInChannel && claimsInChannel.length ? (
<FileList sortByHeight
hideFilter
fileInfos={claimsInChannel}
navigation={navigation}
style={channelPageStyle.fileList}
contentContainerStyle={channelPageStyle.fileListContent}
onEndReached={() => this.setState({ showPageButtons: true })} />
<FileList
sortByHeight
hideFilter
fileInfos={claimsInChannel}
navigation={navigation}
style={channelPageStyle.fileList}
contentContainerStyle={channelPageStyle.fileListContent}
onEndReached={() => this.setState({ showPageButtons: true })}
/>
) : (
<View style={channelPageStyle.busyContainer}>
<Text style={channelPageStyle.infoText}>No content found.</Text>
@ -87,17 +81,23 @@ class ChannelPage extends React.PureComponent {
pageButtons = (
<View style={channelPageStyle.pageButtons}>
<View>
{(this.state.page > 1) && <Button
style={channelPageStyle.button}
text={"Previous"}
disabled={!!fetching}
onPress={this.handlePreviousPage} />}
{this.state.page > 1 && (
<Button
style={channelPageStyle.button}
text={'Previous'}
disabled={!!fetching}
onPress={this.handlePreviousPage}
/>
)}
</View>
{(this.state.page < totalPages) && <Button
style={[channelPageStyle.button, channelPageStyle.nextButton]}
text={"Next"}
disabled={!!fetching}
onPress={this.handleNextPage} />}
{this.state.page < totalPages && (
<Button
style={[channelPageStyle.button, channelPageStyle.nextButton]}
text={'Next'}
disabled={!!fetching}
onPress={this.handleNextPage}
/>
)}
</View>
);
}
@ -108,7 +108,7 @@ class ChannelPage extends React.PureComponent {
{pageButtons}
</View>
);
}
};
renderAbout = () => {
const { claim } = this.props;
@ -126,45 +126,41 @@ class ChannelPage extends React.PureComponent {
const { cover, description, thumbnail, email, website_url, title } = claim.value;
return (
<View style={channelPageStyle.aboutTab}>
{(!website_url && !email && !description) &&
{!website_url && !email && !description && (
<View style={channelPageStyle.busyContainer}>
<Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text>
</View>}
</View>
)}
{(website_url || email || description) &&
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
{(website_url && website_url.trim().length > 0) &&
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutTitle}>Website</Text>
<Link style={channelPageStyle.aboutText} text={website_url} href={website_url} />
</View>}
{(website_url || email || description) && (
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
{website_url && website_url.trim().length > 0 && (
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutTitle}>Website</Text>
<Link style={channelPageStyle.aboutText} text={website_url} href={website_url} />
</View>
)}
{(email && email.trim().length > 0) &&
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutTitle}>Email</Text>
<Link style={channelPageStyle.aboutText} text={email} href={`mailto:${email}`} />
</View>}
{email && email.trim().length > 0 && (
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutTitle}>Email</Text>
<Link style={channelPageStyle.aboutText} text={email} href={`mailto:${email}`} />
</View>
)}
{(description && description.trim().length > 0) &&
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutText}>{description}</Text>
</View>}
</ScrollView>}
{description && description.trim().length > 0 && (
<View style={channelPageStyle.aboutItem}>
<Text style={channelPageStyle.aboutText}>{description}</Text>
</View>
)}
</ScrollView>
)}
</View>
);
}
};
render() {
const {
fetching,
claimsInChannel,
claim,
navigation,
totalPages,
uri,
drawerStack,
popDrawerStack
} = this.props;
const { fetching, claimsInChannel, claim, navigation, totalPages, uri, drawerStack, popDrawerStack } = this.props;
const { name, permanent_url: permanentUrl } = claim;
let thumbnailUrl, coverUrl, title;
@ -187,28 +183,44 @@ class ChannelPage extends React.PureComponent {
<Image
style={channelPageStyle.coverImage}
resizeMode={'cover'}
source={(coverUrl && coverUrl.trim().length > 0) ? { uri: coverUrl } : require('../../assets/default_channel_cover.png')} />
source={
coverUrl && coverUrl.trim().length > 0
? { uri: coverUrl }
: require('../../assets/default_channel_cover.png')
}
/>
<View style={channelPageStyle.channelHeader}>
<Text style={channelPageStyle.channelName}>{(title && title.trim().length > 0) ? title : name}</Text>
<Text style={channelPageStyle.channelName}>{title && title.trim().length > 0 ? title : name}</Text>
</View>
<View style={channelPageStyle.avatarImageContainer}>
<Image
style={channelPageStyle.avatarImage}
resizeMode={'cover'}
source={(thumbnailUrl && thumbnailUrl.trim().length > 0) ? { uri: thumbnailUrl } : require('../../assets/default_avatar.jpg')} />
source={
thumbnailUrl && thumbnailUrl.trim().length > 0
? { uri: thumbnailUrl }
: require('../../assets/default_avatar.jpg')
}
/>
</View>
<SubscribeButton style={channelPageStyle.subscribeButton} uri={uri} name={name} />
</View>
<View style={channelPageStyle.tabBar}>
<TouchableOpacity style={channelPageStyle.tab} onPress={() => this.setState({ activeTab: Constants.CONTENT_TAB })}>
<TouchableOpacity
style={channelPageStyle.tab}
onPress={() => this.setState({ activeTab: Constants.CONTENT_TAB })}
>
<Text style={channelPageStyle.tabTitle}>CONTENT</Text>
{Constants.CONTENT_TAB === this.state.activeTab && <View style={channelPageStyle.activeTabHint} />}
</TouchableOpacity>
<TouchableOpacity style={channelPageStyle.tab} onPress={() => this.setState({ activeTab: Constants.ABOUT_TAB })}>
<TouchableOpacity
style={channelPageStyle.tab}
onPress={() => this.setState({ activeTab: Constants.ABOUT_TAB })}
>
<Text style={channelPageStyle.tabTitle}>ABOUT</Text>
{Constants.ABOUT_TAB === this.state.activeTab && <View style={channelPageStyle.activeTabHint} />}
</TouchableOpacity>
@ -218,7 +230,7 @@ class ChannelPage extends React.PureComponent {
{Constants.ABOUT_TAB === this.state.activeTab && this.renderAbout()}
</View>
</View>
)
);
}
}

View file

@ -1,9 +1,5 @@
import { connect } from 'react-redux';
import {
doFileList,
selectBalance,
selectFileInfosDownloaded,
} from 'lbry-redux';
import { doFileList, selectBalance, selectFileInfosDownloaded } from 'lbry-redux';
import {
doFetchFeaturedUris,
doFetchRewardedContent,
@ -38,7 +34,10 @@ const perform = dispatch => ({
fetchSubscriptions: () => dispatch(doFetchMySubscriptions()),
fileList: () => dispatch(doFileList()),
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value))
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
});
export default connect(select, perform)(DiscoverPage);
export default connect(
select,
perform
)(DiscoverPage);

View file

@ -1,14 +1,6 @@
import React from 'react';
import NavigationActions from 'react-navigation';
import {
Alert,
ActivityIndicator,
Linking,
NativeModules,
SectionList,
Text,
View
} from 'react-native';
import { Alert, ActivityIndicator, Linking, NativeModules, SectionList, Text, View } from 'react-native';
import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
import AsyncStorage from '@react-native-community/async-storage';
import moment from 'moment';
@ -33,22 +25,18 @@ class DiscoverPage extends React.PureComponent {
const delta = now - start;
AsyncStorage.getItem('firstLaunchSuspended').then(suspended => {
AsyncStorage.removeItem('firstLaunchSuspended');
const appSuspended = (suspended === 'true');
const appSuspended = suspended === 'true';
if (NativeModules.Firebase) {
NativeModules.Firebase.track('first_run_time', {
'total_seconds': delta, 'app_suspended': appSuspended
total_seconds: delta,
app_suspended: appSuspended,
});
}
});
}
});
const {
fetchFeaturedUris,
fetchRewardedContent,
fetchSubscriptions,
fileList
} = this.props;
const { fetchFeaturedUris, fetchRewardedContent, fetchSubscriptions, fileList } = this.props;
fetchFeaturedUris();
fetchRewardedContent();
@ -73,14 +61,15 @@ class DiscoverPage extends React.PureComponent {
}
return null;
}
};
componentDidUpdate(prevProps, prevState) {
const { unreadSubscriptions, enabledChannelNotifications } = this.props;
const utility = NativeModules.UtilityModule;
if (utility) {
const hasUnread = prevProps.unreadSubscriptions &&
const hasUnread =
prevProps.unreadSubscriptions &&
prevProps.unreadSubscriptions.length !== unreadSubscriptions.length &&
unreadSubscriptions.length > 0;
@ -98,10 +87,17 @@ class DiscoverPage extends React.PureComponent {
const source = sub.value.stream.source;
const metadata = sub.value.stream.metadata;
if (source) {
isPlayable = source.contentType && ['audio', 'video'].indexOf(source.contentType.substring(0, 5)) > -1;
isPlayable =
source.contentType && ['audio', 'video'].indexOf(source.contentType.substring(0, 5)) > -1;
}
if (metadata) {
utility.showNotificationForContent(uri, metadata.title, channelName, metadata.thumbnail, isPlayable);
utility.showNotificationForContent(
uri,
metadata.title,
channelName,
metadata.thumbnail,
isPlayable
);
}
}
});
@ -122,17 +118,23 @@ class DiscoverPage extends React.PureComponent {
const lastShownTime = parseInt(lastShownParts[0], 10);
const lastShownCount = parseInt(lastShownParts[1], 10);
if (!isNaN(lastShownTime) && !isNaN(lastShownCount)) {
if (now > (lastShownTime + (Constants.RATING_REMINDER_INTERVAL * lastShownCount))) {
if (now > lastShownTime + Constants.RATING_REMINDER_INTERVAL * lastShownCount) {
Alert.alert(
'Enjoying LBRY?',
'Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.',
[
{ text: 'Never ask again', onPress: () => setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true')},
{ text: 'Maybe later', onPress: () => this.updateRatingReminderShown(lastShownCount)},
{ text: 'Rate app', onPress: () => {
setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true');
Linking.openURL(Constants.PLAY_STORE_URL);
}}
{
text: 'Never ask again',
onPress: () => setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true'),
},
{ text: 'Maybe later', onPress: () => this.updateRatingReminderShown(lastShownCount) },
{
text: 'Rate app',
onPress: () => {
setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true');
Linking.openURL(Constants.PLAY_STORE_URL);
},
},
],
{ cancelable: false }
);
@ -144,13 +146,13 @@ class DiscoverPage extends React.PureComponent {
// first time, so set a value for the next interval multiplier
this.updateRatingReminderShown(0);
}
}
};
updateRatingReminderShown = (lastShownCount) => {
updateRatingReminderShown = lastShownCount => {
const { setClientSetting } = this.props;
const settingString = (moment().unix() + '|' + (lastShownCount + 1));
const settingString = moment().unix() + '|' + (lastShownCount + 1);
setClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN, settingString);
}
};
trimClaimIdFromCategory(category) {
return category.split('#')[0];
@ -170,27 +172,24 @@ class DiscoverPage extends React.PureComponent {
<Text style={discoverStyle.title}>Fetching content...</Text>
</View>
)}
{(!!hasContent) &&
(<SectionList
{!!hasContent && (
<SectionList
style={discoverStyle.scrollContainer}
contentContainerStyle={discoverStyle.scrollPadding}
initialNumToRender={4}
maxToRenderPerBatch={4}
removeClippedSubviews={true}
renderItem={ ({item, index, section}) => (
<CategoryList
key={item}
category={item}
categoryMap={featuredUris}
navigation={navigation} />
renderItem={({ item, index, section }) => (
<CategoryList key={item} category={item} categoryMap={featuredUris} navigation={navigation} />
)}
renderSectionHeader={
({section: {title}}) => (<Text style={discoverStyle.categoryName}>{title}</Text>)
}
sections={Object.keys(featuredUris).map(category => ({ title: this.trimClaimIdFromCategory(category), data: [category] }))}
renderSectionHeader={({ section: { title } }) => <Text style={discoverStyle.categoryName}>{title}</Text>}
sections={Object.keys(featuredUris).map(category => ({
title: this.trimClaimIdFromCategory(category),
data: [category],
}))}
keyExtractor={(item, index) => item}
/>)
}
/>
)}
<FloatingWalletBalance navigation={navigation} />
</View>
);

View file

@ -10,7 +10,7 @@ import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants';
import DownloadsPage from './view';
const select = (state) => ({
const select = state => ({
claims: selectMyClaimsWithoutChannels(state),
currentRoute: selectCurrentRoute(state),
fileInfos: selectFileInfosDownloaded(state),
@ -20,7 +20,10 @@ const select = (state) => ({
const perform = dispatch => ({
fileList: () => dispatch(doFileList()),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_MY_LBRY)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(select, perform)(DownloadsPage);
export default connect(
select,
perform
)(DownloadsPage);

View file

@ -1,14 +1,6 @@
import React from 'react';
import { Lbry, buildURI } from 'lbry-redux';
import {
ActivityIndicator,
Button,
FlatList,
Text,
TextInput,
View,
ScrollView
} from 'react-native';
import { ActivityIndicator, Button, FlatList, Text, TextInput, View, ScrollView } from 'react-native';
import { navigateToUri, uriFromFileInfo } from 'utils/helper';
import Colors from 'styles/colors';
import Constants from 'constants';
@ -22,7 +14,7 @@ import fileListStyle from 'styles/fileList';
class DownloadsPage extends React.PureComponent {
static navigationOptions = {
title: 'Downloads'
title: 'Downloads',
};
didFocusListener;
@ -43,7 +35,7 @@ class DownloadsPage extends React.PureComponent {
pushDrawerStack();
setPlayerVisible();
fileList();
}
};
componentDidMount() {
this.onComponentFocused();
@ -64,32 +56,37 @@ class DownloadsPage extends React.PureComponent {
return (
<View style={downloadsStyle.container}>
<UriBar navigation={navigation} />
{!fetching && !hasDownloads &&
{!fetching && !hasDownloads && (
<View style={downloadsStyle.busyContainer}>
<Text style={downloadsStyle.noDownloadsText}>You have not watched or downloaded any content from LBRY yet.</Text>
</View>}
{fetching && !hasDownloads &&
<Text style={downloadsStyle.noDownloadsText}>
You have not watched or downloaded any content from LBRY yet.
</Text>
</View>
)}
{fetching && !hasDownloads && (
<View style={downloadsStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={downloadsStyle.loading} />
</View>}
{hasDownloads &&
</View>
)}
{hasDownloads && (
<View style={downloadsStyle.subContainer}>
<StorageStatsCard fileInfos={fileInfos} />
<FlatList
style={downloadsStyle.scrollContainer}
contentContainerStyle={downloadsStyle.scrollPadding}
renderItem={ ({item}) => (
<FileListItem
style={fileListStyle.item}
uri={uriFromFileInfo(item)}
navigation={navigation}
onPress={() => navigateToUri(navigation, uriFromFileInfo(item), { autoplay: true })} />
)
}
renderItem={({ item }) => (
<FileListItem
style={fileListStyle.item}
uri={uriFromFileInfo(item)}
navigation={navigation}
onPress={() => navigateToUri(navigation, uriFromFileInfo(item), { autoplay: true })}
/>
)}
data={fileInfos}
keyExtractor={(item, index) => item.outpoint}
/>
</View>}
</View>
)}
<FloatingWalletBalance navigation={navigation} />
</View>
);

View file

@ -26,14 +26,14 @@ import {
doFetchCostInfoForUri,
makeSelectCostInfoForUri,
selectRewardContentClaimIds,
selectBlackListedOutpoints
selectBlackListedOutpoints,
} from 'lbryinc';
import {
doStartDownload,
doUpdateDownload,
doCompleteDownload,
doDeleteFile,
doStopDownloadingFile
doStopDownloadingFile,
} from 'redux/actions/file';
import { doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectDrawerStack } from 'redux/selectors/drawer';
@ -77,7 +77,8 @@ const perform = dispatch => ({
purchaseUri: (uri, costInfo, saveFile) => dispatch(doPurchaseUri(uri, costInfo, saveFile)),
deletePurchasedUri: uri => dispatch(doDeletePurchasedUri(uri)),
resolveUri: uri => dispatch(doResolveUri(uri)),
sendTip: (amount, claimId, uri, successCallback, errorCallback) => dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
sendTip: (amount, claimId, uri, successCallback, errorCallback) =>
dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)),
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
startDownload: (uri, outpoint, fileInfo) => dispatch(doStartDownload(uri, outpoint, fileInfo)),
@ -85,4 +86,7 @@ const perform = dispatch => ({
completeDownload: (uri, outpoint, fileInfo) => dispatch(doCompleteDownload(uri, outpoint, fileInfo)),
});
export default connect(select, perform)(FilePage);
export default connect(
select,
perform
)(FilePage);

View file

@ -15,7 +15,7 @@ import {
TouchableOpacity,
TouchableWithoutFeedback,
View,
WebView
WebView,
} from 'react-native';
import { NavigationEvents } from 'react-navigation';
import { navigateBack, navigateToUri } from 'utils/helper';
@ -43,7 +43,7 @@ import uriBarStyle from 'styles/uriBar';
class FilePage extends React.PureComponent {
static navigationOptions = {
title: ''
title: '',
};
tipAmountInput = null;
@ -79,7 +79,7 @@ class FilePage extends React.PureComponent {
uri: null,
uriVars: null,
stopDownloadConfirmed: false,
streamingMode: false
streamingMode: false,
};
}
@ -112,7 +112,7 @@ class FilePage extends React.PureComponent {
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.keepAwakeOn();
}
}
};
componentDidMount() {
this.onComponentFocused();
@ -126,7 +126,7 @@ class FilePage extends React.PureComponent {
purchasedUris: prevPurchasedUris,
navigation,
contentType,
notify
notify,
} = this.props;
const { uri } = navigation.state.params;
const {
@ -135,7 +135,7 @@ class FilePage extends React.PureComponent {
fileInfo,
purchasedUris,
purchaseUriErrorMessage,
streamingUrl
streamingUrl,
} = nextProps;
if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) {
@ -151,7 +151,10 @@ class FilePage extends React.PureComponent {
const mediaType = Lbry.getMediaType(contentType);
const isPlayable = mediaType === 'video' || mediaType === 'audio';
if ((this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) && NativeModules.UtilityModule) {
if (
(this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) &&
NativeModules.UtilityModule
) {
if (purchasedUris.includes(uri)) {
const { nout, txid } = claim;
const outpoint = `${txid}:${nout}`;
@ -187,7 +190,7 @@ class FilePage extends React.PureComponent {
window.currentMediaInfo = {
channel: claim ? claim.channel_name : null,
title: metadata ? metadata.title : claim.name,
uri: this.state.uri
uri: this.state.uri,
};
}
}
@ -204,7 +207,7 @@ class FilePage extends React.PureComponent {
}
}
handleFullscreenToggle = (mode) => {
handleFullscreenToggle = mode => {
this.setState({ fullscreenMode: mode });
StatusBar.setHidden(mode);
if (NativeModules.ScreenOrientation) {
@ -224,7 +227,7 @@ class FilePage extends React.PureComponent {
}
}
}
}
};
onDeletePressed = () => {
const { claim, deleteFile, deletePurchasedUri, fileInfo, navigation } = this.props;
@ -234,24 +237,27 @@ class FilePage extends React.PureComponent {
'Are you sure you want to remove this file from your device?',
[
{ text: 'No' },
{ text: 'Yes', onPress: () => {
const { uri } = navigation.state.params;
deleteFile(`${claim.txid}:${claim.nout}`, true);
deletePurchasedUri(uri);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.deleteDownload(uri);
}
this.setState({
downloadPressed: false,
fileViewLogged: false,
mediaLoaded: false,
stopDownloadConfirmed: false
});
}}
{
text: 'Yes',
onPress: () => {
const { uri } = navigation.state.params;
deleteFile(`${claim.txid}:${claim.nout}`, true);
deletePurchasedUri(uri);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.deleteDownload(uri);
}
this.setState({
downloadPressed: false,
fileViewLogged: false,
mediaLoaded: false,
stopDownloadConfirmed: false,
});
},
},
],
{ cancelable: true }
);
}
};
onStopDownloadPressed = () => {
const { deletePurchasedUri, fileInfo, navigation, notify, stopDownload } = this.props;
@ -261,30 +267,33 @@ class FilePage extends React.PureComponent {
'Are you sure you want to stop downloading this file?',
[
{ text: 'No' },
{ text: 'Yes', onPress: () => {
const { uri } = navigation.state.params;
stopDownload(uri, fileInfo);
deletePurchasedUri(uri);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.deleteDownload(uri);
}
this.setState({
downloadPressed: false,
fileViewLogged: false,
mediaLoaded: false,
stopDownloadConfirmed: true
});
{
text: 'Yes',
onPress: () => {
const { uri } = navigation.state.params;
stopDownload(uri, fileInfo);
deletePurchasedUri(uri);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.deleteDownload(uri);
}
this.setState({
downloadPressed: false,
fileViewLogged: false,
mediaLoaded: false,
stopDownloadConfirmed: true,
});
// 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
notify({
message: 'The download will stop momentarily. You do not need to wait to discover something else.',
});
}}
// 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
notify({
message: 'The download will stop momentarily. You do not need to wait to discover something else.',
});
},
},
],
{ cancelable: true }
);
}
};
componentWillUnmount() {
StatusBar.setHidden(false);
@ -309,32 +318,32 @@ class FilePage extends React.PureComponent {
DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted);
}
handleDownloadStarted = (evt) => {
handleDownloadStarted = evt => {
const { startDownload } = this.props;
const { uri, outpoint, fileInfo } = evt;
startDownload(uri, outpoint, fileInfo);
}
};
handleDownloadUpdated = (evt) => {
handleDownloadUpdated = evt => {
const { updateDownload } = this.props;
const { uri, outpoint, fileInfo, progress } = evt;
updateDownload(uri, outpoint, fileInfo, progress);
}
};
handleDownloadCompleted = (evt) => {
handleDownloadCompleted = evt => {
const { completeDownload } = this.props;
const { uri, outpoint, fileInfo } = evt;
completeDownload(uri, outpoint, fileInfo);
}
};
localUriForFileInfo = (fileInfo) => {
localUriForFileInfo = fileInfo => {
if (!fileInfo) {
return null;
}
return 'file:///' + fileInfo.download_path;
}
};
playerUriForFileInfo = (fileInfo) => {
playerUriForFileInfo = fileInfo => {
const { streamingUrl } = this.props;
if (fileInfo && fileInfo.download_path) {
return this.getEncodedDownloadPath(fileInfo);
@ -347,9 +356,9 @@ class FilePage extends React.PureComponent {
}
return null;
}
};
getEncodedDownloadPath = (fileInfo) => {
getEncodedDownloadPath = fileInfo => {
if (this.state.encodedFilePath) {
return this.state.encodedFilePath;
}
@ -358,36 +367,41 @@ class FilePage extends React.PureComponent {
const encodedFileName = encodeURIComponent(fileName).replace(/!/g, '%21');
const encodedFilePath = fileInfo.download_path.replace(fileName, encodedFileName);
return encodedFilePath;
}
};
linkify = (text) => {
linkify = text => {
let linkifiedContent = [];
let lines = text.split(/\n/g);
linkifiedContent = lines.map((line, i) => {
let tokens = line.split(/\s/g);
let lineContent = tokens.length === 0 ? '' : tokens.map((token, j) => {
let hasSpace = j !== (tokens.length - 1);
let space = hasSpace ? ' ' : '';
let lineContent =
tokens.length === 0
? ''
: tokens.map((token, j) => {
let hasSpace = j !== tokens.length - 1;
let space = hasSpace ? ' ' : '';
if (token.match(/^(lbry|https?):\/\//g)) {
return (
<Link key={j}
style={filePageStyle.link}
href={token}
text={token}
effectOnTap={filePageStyle.linkTapped} />
);
} else {
return token + space;
}
});
if (token.match(/^(lbry|https?):\/\//g)) {
return (
<Link
key={j}
style={filePageStyle.link}
href={token}
text={token}
effectOnTap={filePageStyle.linkTapped}
/>
);
} else {
return token + space;
}
});
lineContent.push("\n");
return (<Text key={i}>{lineContent}</Text>);
lineContent.push('\n');
return <Text key={i}>{lineContent}</Text>;
});
return linkifiedContent;
}
};
checkOrientation = () => {
if (this.state.fullscreenMode) {
@ -405,16 +419,18 @@ class FilePage extends React.PureComponent {
}
if (isLandscape) {
this.playerBackground.setNativeProps({ height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height });
this.playerBackground.setNativeProps({
height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height,
});
} else if (this.state.playerBgHeight > 0) {
this.playerBackground.setNativeProps({ height: this.state.playerBgHeight });
}
}
};
onMediaLoaded = (channelName, title, uri) => {
this.setState({ mediaLoaded: true });
window.currentMediaInfo = { channel: channelName, title, uri };
}
};
onPlaybackStarted = () => {
let timeToStartMillis, timeToStart;
@ -428,25 +444,25 @@ class FilePage extends React.PureComponent {
const { uri } = navigation.state.params;
this.logFileView(uri, claim, timeToStartMillis);
let payload = { 'uri': uri };
let payload = { uri: uri };
if (!isNaN(timeToStart)) {
payload['time_to_start_seconds'] = timeToStart;
payload['time_to_start_ms'] = timeToStartMillis;
}
NativeModules.Firebase.track('play', payload);
}
};
onPlaybackFinished = () => {
if (this.scrollView && this.state.relatedContentY) {
this.scrollView.scrollTo({ x: 0, y: this.state.relatedContentY, animated: true});
this.scrollView.scrollTo({ x: 0, y: this.state.relatedContentY, animated: true });
}
}
};
setRelatedContentPosition = (evt) => {
setRelatedContentPosition = evt => {
if (!this.state.relatedContentY) {
this.setState({ relatedContentY: evt.nativeEvent.layout.y });
}
}
};
logFileView = (uri, claim, timeToStart) => {
if (!claim) {
@ -458,7 +474,7 @@ class FilePage extends React.PureComponent {
const params = {
uri,
outpoint,
claim_id: claimId
claim_id: claimId,
};
if (!isNaN(timeToStart)) {
params.time_to_start = timeToStart;
@ -466,7 +482,7 @@ class FilePage extends React.PureComponent {
Lbryio.call('file', 'view', params).catch(() => {});
this.setState({ fileViewLogged: true });
}
};
handleSendTip = () => {
const { claim, balance, navigation, notify, sendTip } = this.props;
@ -480,26 +496,30 @@ class FilePage extends React.PureComponent {
return;
}
sendTip(tipAmount, claim.claim_id, uri, () => { this.setState({ tipAmount: 0, showTipView: false }) });
}
sendTip(tipAmount, claim.claim_id, uri, () => {
this.setState({ tipAmount: 0, showTipView: false });
});
};
renderTags = (tags) => {
renderTags = tags => {
return tags.map((tag, i) => (
<Text style={filePageStyle.tagItem} key={`${tag}-${i}`}>{tag}</Text>
<Text style={filePageStyle.tagItem} key={`${tag}-${i}`}>
{tag}
</Text>
));
}
};
onFileDownloadButtonPlayed = () => {
const { setPlayerVisible } = this.props;
this.startTime = Date.now();
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
setPlayerVisible();
}
};
onBackButtonPressed = () => {
const { navigation, drawerStack, popDrawerStack } = this.props;
navigateBack(navigation, drawerStack, popDrawerStack);
}
};
onSaveFilePressed = () => {
const { costInfo, fileGet, fileInfo, navigation, purchasedUris, purchaseUri } = this.props;
@ -509,13 +529,16 @@ class FilePage extends React.PureComponent {
// file already in library or URI already purchased, use fileGet directly
this.setState({ fileGetStarted: true }, () => fileGet(uri, true));
} else {
this.setState({
downloadPressed: true,
autoPlayMedia: false,
stopDownloadConfirmed: false
}, () => purchaseUri(uri, costInfo, true));
this.setState(
{
downloadPressed: true,
autoPlayMedia: false,
stopDownloadConfirmed: false,
},
() => purchaseUri(uri, costInfo, true)
);
}
}
};
render() {
const {
@ -534,7 +557,7 @@ class FilePage extends React.PureComponent {
position,
purchaseUri,
thumbnail,
title
title,
} = this.props;
const { uri, autoplay } = navigation.state.params;
@ -542,23 +565,22 @@ class FilePage extends React.PureComponent {
if ((isResolvingUri && !claim) || !claim) {
innerContent = (
<View style={filePageStyle.container}>
{isResolvingUri &&
<View style={filePageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
</View>}
{claim === null && !isResolvingUri &&
{isResolvingUri && (
<View style={filePageStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
</View>
)}
{claim === null && !isResolvingUri && (
<View style={filePageStyle.container}>
<Text style={filePageStyle.emptyClaimText}>There's nothing at this location.</Text>
</View>
}
)}
<UriBar value={uri} navigation={navigation} />
</View>
);
} else if (claim && claim.name.length && claim.name[0] === '@') {
innerContent = (
<ChannelPage uri={uri} navigation={navigation} />
);
innerContent = <ChannelPage uri={uri} navigation={navigation} />;
} else if (claim) {
let isClaimBlackListed = false;
@ -577,7 +599,8 @@ class FilePage extends React.PureComponent {
<View style={filePageStyle.pageContainer}>
<View style={filePageStyle.dmcaContainer}>
<Text style={filePageStyle.dmcaText}>
In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.
In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked
access to this content from our applications.
</Text>
<Link style={filePageStyle.dmcaLink} href="https://lbry.com/faq/dmca" text="Read More" />
</View>
@ -585,7 +608,6 @@ class FilePage extends React.PureComponent {
</View>
);
} else {
let tags = [];
if (claim && claim.value && claim.value.tags) {
tags = claim.value.tags;
@ -597,27 +619,38 @@ class FilePage extends React.PureComponent {
const mediaType = Lbry.getMediaType(contentType);
const isPlayable = mediaType === 'video' || mediaType === 'audio';
const { height, channel_name: channelName, value } = claim;
const showActions = (fileInfo && fileInfo.download_path) &&
const showActions =
fileInfo &&
fileInfo.download_path &&
!this.state.fullscreenMode &&
!this.state.showImageViewer &&
!this.state.showWebView;
const showFileActions = (fileInfo && fileInfo.download_path) &&
const showFileActions =
fileInfo &&
fileInfo.download_path &&
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
const canSendTip = this.state.tipAmount > 0;
const fullChannelUri = channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
const fullChannelUri =
channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
const playerStyle = [filePageStyle.player,
this.state.isLandscape ? filePageStyle.containedPlayerLandscape :
(this.state.fullscreenMode ? filePageStyle.fullscreenPlayer : filePageStyle.containedPlayer)];
const playerStyle = [
filePageStyle.player,
this.state.isLandscape
? filePageStyle.containedPlayerLandscape
: this.state.fullscreenMode
? filePageStyle.fullscreenPlayer
: filePageStyle.containedPlayer,
];
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
// at least 2MB (or the full download) before media can be loaded
const canLoadMedia = (this.state.streamingMode) || (fileInfo &&
(fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes)); // 2MB = 1024*1024*2
const isViewable = (mediaType === 'image' || mediaType === 'text');
const canLoadMedia =
this.state.streamingMode ||
(fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes)); // 2MB = 1024*1024*2
const isViewable = mediaType === 'image' || mediaType === 'text';
const isWebViewable = mediaType === 'text';
const canOpen = isViewable && completed;
const canOpen = isViewable && completed;
const localFileUri = this.localUriForFileInfo(fileInfo);
const openFile = () => {
@ -625,10 +658,12 @@ class FilePage extends React.PureComponent {
// use image viewer
if (!this.state.showImageViewer) {
this.setState({
imageUrls: [{
url: localFileUri
}],
showImageViewer: true
imageUrls: [
{
url: localFileUri,
},
],
showImageViewer: true,
});
}
}
@ -636,13 +671,18 @@ class FilePage extends React.PureComponent {
// show webview
if (!this.state.showWebView) {
this.setState({
showWebView: true
showWebView: true,
});
}
}
}
};
if (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && 'true' === this.state.uriVars.download) {
if (
fileInfo &&
!this.state.autoDownloadStarted &&
this.state.uriVars &&
'true' === this.state.uriVars.download
) {
this.setState({ autoDownloadStarted: true }, () => {
purchaseUri(uri, costInfo, !isPlayable);
if (NativeModules.UtilityModule) {
@ -659,57 +699,86 @@ class FilePage extends React.PureComponent {
innerContent = (
<View style={filePageStyle.pageContainer}>
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
{this.state.showWebView && isWebViewable && <WebView source={{ uri: localFileUri }}
style={filePageStyle.viewer} />}
{this.state.showWebView && isWebViewable && (
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
)}
{this.state.showImageViewer && <ImageViewer style={StyleSheet.flatten(filePageStyle.viewer)}
imageUrls={this.state.imageUrls}
renderIndicator={() => null} />}
{this.state.showImageViewer && (
<ImageViewer
style={StyleSheet.flatten(filePageStyle.viewer)}
imageUrls={this.state.imageUrls}
renderIndicator={() => null}
/>
)}
{!this.state.showWebView && (
<View style={this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer}
onLayout={this.checkOrientation}>
<View
style={
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
}
onLayout={this.checkOrientation}
>
<View style={filePageStyle.mediaContainer}>
{((canOpen || (!fileInfo || (isPlayable && !canLoadMedia))) || (!canOpen && fileInfo)) &&
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />}
{((!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded) &&
<ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />}
{((isPlayable && !completed && !canLoadMedia) || canOpen || (!completed && !this.state.streamingMode)) &&
(!this.state.downloadPressed) &&
<FileDownloadButton uri={uri}
style={filePageStyle.downloadButton}
openFile={openFile}
isPlayable={isPlayable}
isViewable={isViewable}
onPlay={this.onFileDownloadButtonPlayed}
onView={() => this.setState({ downloadPressed: true })}
onButtonLayout={() => this.setState({ downloadButtonShown: true })} />}
{!fileInfo && <FilePrice uri={uri} style={filePageStyle.filePriceContainer} textStyle={filePageStyle.filePriceText} />}
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />
)}
{(!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded && (
<ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />
)}
{((isPlayable && !completed && !canLoadMedia) ||
canOpen ||
(!completed && !this.state.streamingMode)) &&
!this.state.downloadPressed && (
<FileDownloadButton
uri={uri}
style={filePageStyle.downloadButton}
openFile={openFile}
isPlayable={isPlayable}
isViewable={isViewable}
onPlay={this.onFileDownloadButtonPlayed}
onView={() => this.setState({ downloadPressed: true })}
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
/>
)}
{!fileInfo && (
<FilePrice
uri={uri}
style={filePageStyle.filePriceContainer}
textStyle={filePageStyle.filePriceText}
/>
)}
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
<Icon name={"arrow-left"} size={18} style={filePageStyle.backButtonIcon} />
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
</TouchableOpacity>
</View>
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<View
style={playerBgStyle}
ref={ref => {
this.playerBackground = ref;
}}
onLayout={evt => {
if (!this.state.playerBgHeight) {
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
}
}}
/>
)}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
<View style={playerBgStyle}
ref={(ref) => { this.playerBackground = ref; }}
onLayout={(evt) => {
if (!this.state.playerBgHeight) {
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
}
}} />}
{((this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && this.state.fullscreenMode) &&
<View style={fsPlayerBgStyle} />}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
this.state.fullscreenMode && <View style={fsPlayerBgStyle} />}
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
<MediaPlayer
claim={claim}
assignPlayer={(ref) => { this.player = ref; }}
assignPlayer={ref => {
this.player = ref;
}}
uri={uri}
source={this.playerUriForFileInfo(fileInfo)}
style={playerStyle}
autoPlay={autoplay || this.state.autoPlayMedia}
onFullscreenToggled={this.handleFullscreenToggle}
onLayout={(evt) => {
onLayout={evt => {
if (!this.state.playerHeight) {
this.setState({ playerHeight: evt.nativeEvent.layout.height });
}
@ -720,125 +789,168 @@ class FilePage extends React.PureComponent {
onPlaybackFinished={this.onPlaybackFinished}
thumbnail={thumbnail}
position={position}
/>}
/>
)}
{(showActions && showFileActions) &&
<View style={filePageStyle.actions}>
{showFileActions &&
<View style={filePageStyle.fileActions}>
{completed && <Button style={filePageStyle.actionButton}
theme={"light"}
icon={"trash"}
text={"Delete"}
onPress={this.onDeletePressed} />}
{!completed && fileInfo && !fileInfo.stopped &&
fileInfo.written_bytes < fileInfo.total_bytes &&
!this.state.stopDownloadConfirmed &&
<Button style={filePageStyle.actionButton}
icon={"stop"}
theme={"light"}
text={"Stop Download"}
onPress={this.onStopDownloadPressed} />
}
</View>}
</View>}
{showActions && showFileActions && (
<View style={filePageStyle.actions}>
{showFileActions && (
<View style={filePageStyle.fileActions}>
{completed && (
<Button
style={filePageStyle.actionButton}
theme={'light'}
icon={'trash'}
text={'Delete'}
onPress={this.onDeletePressed}
/>
)}
{!completed &&
fileInfo &&
!fileInfo.stopped &&
fileInfo.written_bytes < fileInfo.total_bytes &&
!this.state.stopDownloadConfirmed && (
<Button
style={filePageStyle.actionButton}
icon={'stop'}
theme={'light'}
text={'Stop Download'}
onPress={this.onStopDownloadPressed}
/>
)}
</View>
)}
</View>
)}
<ScrollView
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}
keyboardShouldPersistTaps={'handled'}
ref={(ref) => { this.scrollView = ref; }}>
<TouchableWithoutFeedback style={filePageStyle.titleTouch}
onPress={() => this.setState({ showDescription: !this.state.showDescription })}>
ref={ref => {
this.scrollView = ref;
}}
>
<TouchableWithoutFeedback
style={filePageStyle.titleTouch}
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
>
<View style={filePageStyle.titleRow}>
<Text style={filePageStyle.title} selectable={true}>{title}</Text>
<Text style={filePageStyle.title} selectable={true}>
{title}
</Text>
<View style={filePageStyle.descriptionToggle}>
<Icon name={this.state.showDescription ? "caret-up" : "caret-down"} size={24} />
<Icon name={this.state.showDescription ? 'caret-up' : 'caret-down'} size={24} />
</View>
</View>
</TouchableWithoutFeedback>
{channelName &&
{channelName && (
<View style={filePageStyle.channelRow}>
<View style={filePageStyle.publishInfo}>
<Link style={filePageStyle.channelName}
selectable={true}
text={channelName}
numberOfLines={1}
ellipsizeMode={"tail"}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}} />
<Link
style={filePageStyle.channelName}
selectable={true}
text={channelName}
numberOfLines={1}
ellipsizeMode={'tail'}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
}}
/>
<DateTime
style={filePageStyle.publishDate}
textStyle={filePageStyle.publishDateText}
uri={uri}
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
show={DateTime.SHOW_DATE} />
show={DateTime.SHOW_DATE}
/>
</View>
<View style={filePageStyle.subscriptionRow}>
{((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) &&
<Button style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
theme={"light"}
icon={"download"}
onPress={this.onSaveFilePressed} />}
<Button style={[filePageStyle.actionButton, filePageStyle.tipButton]}
theme={"light"}
icon={"gift"}
onPress={() => this.setState({ showTipView: true })} />
{((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
<Button
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
theme={'light'}
icon={'download'}
onPress={this.onSaveFilePressed}
/>
)}
<Button
style={[filePageStyle.actionButton, filePageStyle.tipButton]}
theme={'light'}
icon={'gift'}
onPress={() => this.setState({ showTipView: true })}
/>
<SubscribeButton
style={filePageStyle.actionButton}
uri={fullChannelUri}
name={channelName}
hideText={false} />
hideText={false}
/>
<SubscribeNotificationButton
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
uri={fullChannelUri}
name={channelName} />
name={channelName}
/>
</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 ref={ref => this.tipAmountInput = ref}
onChangeText={value => this.setState({tipAmount: value})}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.tipAmount}
style={[filePageStyle.input, filePageStyle.tipAmountInput]} />
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
{this.state.showTipView && (
<View style={filePageStyle.tipCard}>
<View style={filePageStyle.row}>
<View style={filePageStyle.amountRow}>
<TextInput
ref={ref => (this.tipAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
keyboardType={'numeric'}
placeholder={'0'}
value={this.state.tipAmount}
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
/>
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
</View>
<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}
onPress={this.handleSendTip}
/>
</View>
<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}
onPress={this.handleSendTip} />
</View>
</View>}
)}
{(this.state.showDescription && description && description.length > 0) && <View style={filePageStyle.divider} />}
{(this.state.showDescription && description) && (
{this.state.showDescription && description && description.length > 0 && (
<View style={filePageStyle.divider} />
)}
{this.state.showDescription && description && (
<View>
<Text style={filePageStyle.description} selectable={true}>{this.linkify(description)}</Text>
<Text style={filePageStyle.description} selectable={true}>
{this.linkify(description)}
</Text>
{tags && tags.length > 0 && (
<View style={filePageStyle.tagContainer}>
<Text style={filePageStyle.tagTitle}>Tags</Text>
<View style={filePageStyle.tagList}>{this.renderTags(tags)}</View>
</View>
)}
</View>)}
</View>
)}
{(costInfo && parseFloat(costInfo.cost) > balance) && <FileRewardsDriver navigation={navigation} />}
{costInfo && parseFloat(costInfo.cost) > balance && <FileRewardsDriver navigation={navigation} />}
<View onLayout={this.setRelatedContentPosition} />
<RelatedContent navigation={navigation} uri={uri} />
</ScrollView>
</View>
)}
{(!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView) &&
<FloatingWalletBalance navigation={navigation} />}
{!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView && (
<FloatingWalletBalance navigation={navigation} />
)}
</View>
);
}

View file

@ -24,7 +24,7 @@ import {
import { doSetClientSetting } from 'redux/actions/settings';
import FirstRun from './view';
const select = (state) => ({
const select = state => ({
authenticating: selectAuthenticationIsPending(state),
authToken: selectAuthToken(state),
emailToVerify: selectEmailToVerify(state),
@ -48,7 +48,10 @@ const perform = dispatch => ({
checkSync: () => dispatch(doCheckSync()),
setDefaultAccount: () => dispatch(doSetDefaultAccount()),
notify: data => dispatch(doToast(data)),
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
});
export default connect(select, perform)(FirstRun);
export default connect(
select,
perform
)(FirstRun);

View file

@ -1,12 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
NativeModules,
Platform,
Text,
TextInput,
View
} from 'react-native';
import { NativeModules, Platform, Text, TextInput, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
import Constants from 'constants';
@ -16,7 +10,7 @@ class EmailCollectPage extends React.PureComponent {
state = {
email: null,
placeholder: 'you@example.com',
verifying: true
verifying: true,
};
componentWillReceiveProps(nextProps) {
@ -34,16 +28,16 @@ class EmailCollectPage extends React.PureComponent {
}
}
handleChangeText = (text) => {
handleChangeText = text => {
// save the value to the state email
const { onEmailChanged } = this.props;
this.setState({ email: text });
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
if (onEmailChanged) {
onEmailChanged(text);
}
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
}
};
render() {
const { onEmailViewLayout } = this.props;
@ -51,33 +45,34 @@ class EmailCollectPage extends React.PureComponent {
const content = (
<View onLayout={onEmailViewLayout}>
<Text style={firstRunStyle.title}>Setup account</Text>
<TextInput style={firstRunStyle.emailInput}
placeholder={this.state.placeholder}
underlineColorAndroid="transparent"
selectionColor={Colors.NextLbryGreen}
value={this.state.email}
onChangeText={text => this.handleChangeText(text)}
onFocus={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: '' });
}
}}
onBlur={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: 'you@example.com' });
}
}}
/>
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
<TextInput
style={firstRunStyle.emailInput}
placeholder={this.state.placeholder}
underlineColorAndroid="transparent"
selectionColor={Colors.NextLbryGreen}
value={this.state.email}
onChangeText={text => this.handleChangeText(text)}
onFocus={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: '' });
}
}}
onBlur={() => {
if (!this.state.email || this.state.email.length === 0) {
this.setState({ placeholder: 'you@example.com' });
}
}}
/>
<Text style={firstRunStyle.paragraph}>
An account will allow you to earn rewards and keep your account and settings synced.
</Text>
<Text style={firstRunStyle.infoParagraph}>
This information is disclosed only to LBRY, Inc. and not to the LBRY network.
</Text>
</View>
);
return (
<View style={firstRunStyle.container}>
{content}
</View>
);
return <View style={firstRunStyle.container}>{content}</View>;
}
}

View file

@ -1,15 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
ActivityIndicator,
Linking,
NativeModules,
Platform,
Switch,
Text,
TextInput,
View
} from 'react-native';
import { ActivityIndicator, Linking, NativeModules, Platform, Switch, Text, TextInput, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Colors from 'styles/colors';
@ -23,7 +14,7 @@ class EmailVerifyPage extends React.PureComponent {
resendVerificationEmail(email);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
}
};
render() {
const { onEmailViewLayout, email } = this.props;
@ -31,19 +22,26 @@ class EmailVerifyPage extends React.PureComponent {
const content = (
<View onLayout={onEmailViewLayout}>
<Text style={firstRunStyle.title}>Verify Email</Text>
<Text style={firstRunStyle.paragraph}>An email has been sent to <Text style={firstRunStyle.nowrap} numberOfLines={1}>{email}</Text>. Please follow the instructions in the message to verify your email address.</Text>
<Text style={firstRunStyle.paragraph}>
An email has been sent to{' '}
<Text style={firstRunStyle.nowrap} numberOfLines={1}>
{email}
</Text>
. Please follow the instructions in the message to verify your email address.
</Text>
<View style={firstRunStyle.buttonContainer}>
<Button style={firstRunStyle.verificationButton} theme={"light"} text={"Resend"} onPress={this.onResendPressed} />
<Button
style={firstRunStyle.verificationButton}
theme={'light'}
text={'Resend'}
onPress={this.onResendPressed}
/>
</View>
</View>
);
return (
<View style={firstRunStyle.container}>
{content}
</View>
);
return <View style={firstRunStyle.container}>{content}</View>;
}
}

View file

@ -9,7 +9,7 @@ import {
Switch,
Text,
TextInput,
View
View,
} from 'react-native';
import Colors from 'styles/colors';
import Constants from 'constants';
@ -18,7 +18,7 @@ import firstRunStyle from 'styles/firstRun';
class SkipAccountPage extends React.PureComponent {
state = {
confirmed: false
confirmed: false,
};
render() {
@ -30,22 +30,29 @@ class SkipAccountPage extends React.PureComponent {
<Icon name="exclamation-triangle" style={firstRunStyle.titleIcon} size={32} color={Colors.White} />
<Text style={firstRunStyle.title}>Are you sure?</Text>
</View>
<Text style={firstRunStyle.paragraph}>Without an account, you will not receive rewards, sync and backup services, or security updates.</Text>
<Text style={firstRunStyle.paragraph}>
Without an account, you will not receive rewards, sync and backup services, or security updates.
</Text>
<View style={[firstRunStyle.row, firstRunStyle.confirmContainer]}>
<View style={firstRunStyle.rowSwitch}>
<Switch value={this.state.confirmed} onValueChange={value => { this.setState({ confirmed: value }); onSkipSwitchChanged(value); }} />
<Switch
value={this.state.confirmed}
onValueChange={value => {
this.setState({ confirmed: value });
onSkipSwitchChanged(value);
}}
/>
</View>
<Text style={firstRunStyle.rowParagraph}>I understand that by uninstalling LBRY I will lose any balances or published content with no recovery option.</Text>
<Text style={firstRunStyle.rowParagraph}>
I understand that by uninstalling LBRY I will lose any balances or published content with no recovery
option.
</Text>
</View>
</View>
);
return (
<View style={firstRunStyle.container}>
{content}
</View>
);
return <View style={firstRunStyle.container}>{content}</View>;
}
}

View file

@ -9,7 +9,7 @@ import {
Text,
TextInput,
TouchableOpacity,
View
View,
} from 'react-native';
import { BarPasswordStrengthDisplay } from 'react-native-password-strength-meter';
import AsyncStorage from '@react-native-community/async-storage';
@ -27,7 +27,7 @@ class WalletPage extends React.PureComponent {
statusTries: 0,
walletReady: false,
hasCheckedSync: false,
revealPassword: false
revealPassword: false,
};
componentDidMount() {
@ -36,28 +36,30 @@ class WalletPage extends React.PureComponent {
checkWalletReady = () => {
// make sure the sdk wallet component is ready
Lbry.status().then(status => {
if (status.startup_status && status.startup_status.wallet) {
this.setState({ walletReady: true }, () => {
this.props.checkSync();
setTimeout(() => this.setState({ hasCheckedSync: true}), 1000);
});
return;
}
setTimeout(this.checkWalletReady, 1000);
}).catch((e) => {
setTimeout(this.checkWalletReady, 1000);
});
}
Lbry.status()
.then(status => {
if (status.startup_status && status.startup_status.wallet) {
this.setState({ walletReady: true }, () => {
this.props.checkSync();
setTimeout(() => this.setState({ hasCheckedSync: true }), 1000);
});
return;
}
setTimeout(this.checkWalletReady, 1000);
})
.catch(e => {
setTimeout(this.checkWalletReady, 1000);
});
};
handleChangeText = (text) => {
handleChangeText = text => {
// save the value to the state email
const { onPasswordChanged } = this.props;
this.setState({ password: text });
if (onPasswordChanged) {
onPasswordChanged(text);
}
}
};
render() {
const { onPasswordChanged, onWalletViewLayout, getSyncIsPending, hasSyncedWallet, syncApplyIsPending } = this.props;
@ -74,7 +76,7 @@ class WalletPage extends React.PureComponent {
content = (
<View style={firstRunStyle.centered}>
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} />
<Text style={firstRunStyle.paragraph}>Validating password...</Text>
<Text style={firstRunStyle.paragraph}>Validating password...</Text>
</View>
);
} else {
@ -82,11 +84,13 @@ class WalletPage extends React.PureComponent {
<View onLayout={onWalletViewLayout}>
<Text style={firstRunStyle.title}>Password</Text>
<Text style={firstRunStyle.paragraph}>
{hasSyncedWallet ? "Please enter the password you used to secure your wallet." :
"Please enter a password to secure your account and wallet."}
{hasSyncedWallet
? 'Please enter the password you used to secure your wallet.'
: 'Please enter a password to secure your account and wallet.'}
</Text>
<View style={firstRunStyle.passwordInputContainer}>
<TextInput style={firstRunStyle.passwordInput}
<TextInput
style={firstRunStyle.passwordInput}
placeholder={this.state.placeholder}
underlineColorAndroid="transparent"
selectionColor={Colors.NextLbryGreen}
@ -103,31 +107,32 @@ class WalletPage extends React.PureComponent {
this.setState({ placeholder: 'password' });
}
}}
/>
/>
<TouchableOpacity
style={firstRunStyle.revealPasswordIcon}
onPress={() => this.setState({ revealPassword: !this.state.revealPassword })}>
<Icon name={this.state.revealPassword ? "eye-slash" : "eye"} size={16} style={firstRunStyle.revealIcon} />
onPress={() => this.setState({ revealPassword: !this.state.revealPassword })}
>
<Icon name={this.state.revealPassword ? 'eye-slash' : 'eye'} size={16} style={firstRunStyle.revealIcon} />
</TouchableOpacity>
</View>
{(!hasSyncedWallet && this.state.password && this.state.password.trim().length) > 0 &&
{(!hasSyncedWallet && this.state.password && this.state.password.trim().length) > 0 && (
<View style={firstRunStyle.passwordStrength}>
<BarPasswordStrengthDisplay
width={Dimensions.get('window').width - firstRunMargins}
minLength={1}
password={this.state.password} />
</View>}
<Text style={firstRunStyle.infoParagraph}>Note: for wallet security purposes, LBRY is unable to reset your password.</Text>
password={this.state.password}
/>
</View>
)}
<Text style={firstRunStyle.infoParagraph}>
Note: for wallet security purposes, LBRY is unable to reset your password.
</Text>
</View>
);
}
return (
<View style={firstRunStyle.container}>
{content}
</View>
);
return <View style={firstRunStyle.container}>{content}</View>;
}
}

View file

@ -1,12 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
ActivityIndicator,
NativeModules,
Platform,
Text,
View
} from 'react-native';
import { ActivityIndicator, NativeModules, Platform, Text, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
import Constants from 'constants';
@ -53,25 +47,27 @@ class WelcomePage extends React.PureComponent {
const { authenticate } = this.props;
this.setState({ authenticationStarted: true, authenticationFailed: false });
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
Lbry.status().then(info => {
this.setState({ sdkStarted: true });
Lbry.status()
.then(info => {
this.setState({ sdkStarted: true });
authenticate(appVersion, Platform.OS);
}).catch(error => {
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
this.setState({ authenticationFailed: true });
authenticate(appVersion, Platform.OS);
})
.catch(error => {
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
this.setState({ authenticationFailed: true });
// sdk_start_failed
NativeModules.Firebase.track('sdk_start_failed', null);
} else {
setTimeout(() => {
this.startAuthenticating();
this.setState({ statusTries: this.state.statusTries + 1 });
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
}
});
// sdk_start_failed
NativeModules.Firebase.track('sdk_start_failed', null);
} else {
setTimeout(() => {
this.startAuthenticating();
this.setState({ statusTries: this.state.statusTries + 1 });
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
}
});
});
}
};
render() {
const { authenticating, authToken, onWelcomePageLayout } = this.props;
@ -81,7 +77,10 @@ class WelcomePage extends React.PureComponent {
// Ask the user to try again
content = (
<View>
<Text style={firstRunStyle.paragraph}>The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again.</Text>
<Text style={firstRunStyle.paragraph}>
The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the
app to try again.
</Text>
</View>
);
} else if (!authToken || authenticating) {
@ -95,16 +94,15 @@ class WelcomePage extends React.PureComponent {
content = (
<View onLayout={onWelcomePageLayout}>
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
<Text style={firstRunStyle.paragraph}>
LBRY is a community-controlled content platform where you can find and publish videos, music, books, and
more.
</Text>
</View>
);
}
return (
<View style={firstRunStyle.container}>
{content}
</View>
);
return <View style={firstRunStyle.container}>{content}</View>;
}
}

View file

@ -1,13 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
ActivityIndicator,
Linking,
NativeModules,
Text,
TouchableOpacity,
View
} from 'react-native';
import { ActivityIndicator, Linking, NativeModules, Text, TouchableOpacity, View } from 'react-native';
import { NavigationActions, StackActions } from 'react-navigation';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
@ -39,11 +32,11 @@ class FirstRunScreen extends React.PureComponent {
skipAccountConfirmed: false,
showBottomContainer: false,
walletPassword: null,
syncApplyStarted: false
syncApplyStarted: false,
};
componentDidMount() {
Linking.getInitialURL().then((url) => {
Linking.getInitialURL().then(url => {
if (url) {
this.setState({ launchUrl: url });
}
@ -72,10 +65,10 @@ class FirstRunScreen extends React.PureComponent {
if (this.state.emailSubmitted && !emailNewPending) {
this.setState({ emailSubmitted: false });
if (emailNewErrorMessage && emailNewErrorMessage.trim().length > 0) {
notify ({ message: String(emailNewErrorMessage), isError: true });
notify({ message: String(emailNewErrorMessage), isError: true });
} else {
// Request successful. Navigate to email verify page.
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_VERIFY)
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_VERIFY);
}
}
@ -98,25 +91,26 @@ class FirstRunScreen extends React.PureComponent {
this.checkVerificationStatus(user);
}
checkVerificationStatus = (user) => {
checkVerificationStatus = user => {
const { navigation } = this.props;
this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email),
}, () => {
if (this.state.isEmailVerified) {
this.showPage(Constants.FIRST_RUN_PAGE_WALLET);
this.setState(
{
isEmailVerified: user && user.primary_email && user.has_verified_email,
},
() => {
if (this.state.isEmailVerified) {
this.showPage(Constants.FIRST_RUN_PAGE_WALLET);
}
}
});
}
);
};
launchSplashScreen() {
const { navigation } = this.props;
const resetAction = StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Splash', params: { launchUri: this.state.launchUri } })
]
actions: [NavigationActions.navigate({ routeName: 'Splash', params: { launchUri: this.state.launchUri } })],
});
navigation.dispatch(resetAction);
}
@ -136,23 +130,19 @@ class FirstRunScreen extends React.PureComponent {
if (Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) {
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_COLLECT);
}
}
};
checkWalletPassword = () => {
const { syncApply, syncHash, syncData } = this.props;
this.setState({ syncApplyStarted: true, showBottomContainer: false }, () => {
syncApply(syncHash, syncData, this.state.walletPassword);
});
}
};
handleContinuePressed = () => {
const { notify, user, hasSyncedWallet } = this.props;
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
if (!this.state.walletPassword || this.state.walletPassword.trim().length === 0) {
return notify({ message: 'Please enter a wallet password' });
}
// do apply sync to check if the password is valid
if (hasSyncedWallet) {
this.checkWalletPassword();
@ -167,7 +157,10 @@ class FirstRunScreen extends React.PureComponent {
return;
}
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage && pageIndex === (FirstRunScreen.pages.length - 1)) {
if (
Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage &&
pageIndex === FirstRunScreen.pages.length - 1
) {
this.closeFinalPage();
} else {
// TODO: Actions and page verification for specific pages
@ -178,37 +171,35 @@ class FirstRunScreen extends React.PureComponent {
this.showNextPage();
}
}
}
};
handleEmailCollectPageContinue() {
const { notify, addUserEmail } = this.props;
const { email } = this.state;
// validate the email
if (!email || email.indexOf('@') === -1) {
return notify({
message: 'Please provide a valid email address to continue.',
});
}
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
// validate the email
if (!email || email.indexOf('@') === -1) {
return notify({
message: 'Please provide a valid email address to continue.',
});
}
addUserEmail(email);
this.setState({ emailSubmitted: true });
});
addUserEmail(email);
this.setState({ emailSubmitted: true });
}
checkBottomContainer = (pageName) => {
checkBottomContainer = pageName => {
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === pageName || Constants.FIRST_RUN_PAGE_WALLET === pageName) {
// do not show the buttons (because we're waiting to get things ready)
this.setState({ showBottomContainer: false });
}
}
};
showNextPage = () => {
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
const nextPage = FirstRunScreen.pages[pageIndex + 1];
this.setState({ currentPage: nextPage });
this.checkBottomContainer(nextPage);
}
};
showPage(pageName) {
const pageIndex = FirstRunScreen.pages.indexOf(pageName);
@ -228,52 +219,50 @@ class FirstRunScreen extends React.PureComponent {
this.launchSplashScreen();
}
onEmailChanged = (email) => {
onEmailChanged = email => {
this.setState({ email });
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT == this.state.currentPage) {
this.setState({ showSkip: (!email || email.trim().length === 0) });
this.setState({ showSkip: !email || email.trim().length === 0 });
} else {
this.setState({ showSkip: false });
}
}
};
onEmailViewLayout = () => {
this.setState({ showBottomContainer: true });
AsyncStorage.getItem('firstRunEmail').then(email => {
this.setState({ showSkip: !email || email.trim().length === 0 });
});
}
this.setState({ showBottomContainer: true, showSkip: true });
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
AsyncStorage.removeItem(Constants.KEY_EMAIL_VERIFY_PENDING);
};
onWalletPasswordChanged = (password) => {
onWalletPasswordChanged = password => {
this.setState({ walletPassword: password });
}
};
onWalletViewLayout = () => {
this.setState({ showBottomContainer: true });
}
};
onWelcomePageLayout = () => {
this.setState({ showBottomContainer: true });
}
};
onSkipSwitchChanged = (checked) => {
onSkipSwitchChanged = checked => {
this.setState({ skipAccountConfirmed: checked });
}
};
setFreshPassword = () => {
const { getSync, setClientSetting } = this.props;
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.setSecureValue(Constants.KEY_FIRST_RUN_PASSWORD, this.state.walletPassword);
Lbry.account_encrypt({ new_password: this.state.walletPassword }).then(() => {
Lbry.account_unlock({ password: this.state.walletPassword }).then(() => {
// fresh account, new password set
getSync(this.state.walletPassword);
setClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED, true);
this.closeFinalPage();
});
const newPassword = this.state.walletPassword ? this.state.walletPassword : '';
NativeModules.UtilityModule.setSecureValue(Constants.KEY_FIRST_RUN_PASSWORD, newPassword);
Lbry.account_encrypt({ new_password: newPassword }).then(() => {
// fresh account, new password set
getSync(newPassword);
setClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED, true);
this.closeFinalPage();
});
}
}
};
render() {
const {
@ -289,84 +278,113 @@ class FirstRunScreen extends React.PureComponent {
getSyncIsPending,
syncApplyIsPending,
resendVerificationEmail,
user
user,
} = this.props;
let page = null;
switch (this.state.currentPage) {
case Constants.FIRST_RUN_PAGE_WELCOME:
page = (<WelcomePage
authenticating={authenticating}
authToken={authToken}
authenticate={authenticate}
onWelcomePageLayout={this.onWelcomePageLayout} />);
page = (
<WelcomePage
authenticating={authenticating}
authToken={authToken}
authenticate={authenticate}
onWelcomePageLayout={this.onWelcomePageLayout}
/>
);
break;
case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT:
page = (<EmailCollectPage
user={user}
showNextPage={this.showNextPage}
onEmailChanged={this.onEmailChanged}
onEmailViewLayout={this.onEmailViewLayout} />);
page = (
<EmailCollectPage
user={user}
showNextPage={this.showNextPage}
onEmailChanged={this.onEmailChanged}
onEmailViewLayout={this.onEmailViewLayout}
/>
);
break;
case Constants.FIRST_RUN_PAGE_EMAIL_VERIFY:
page = (<EmailVerifyPage
onEmailViewLayout={this.onEmailViewLayout}
email={this.state.email}
notify={notify}
resendVerificationEmail={resendVerificationEmail} />);
page = (
<EmailVerifyPage
onEmailViewLayout={this.onEmailViewLayout}
email={this.state.email}
notify={notify}
resendVerificationEmail={resendVerificationEmail}
/>
);
break;
case Constants.FIRST_RUN_PAGE_WALLET:
page = (<WalletPage
checkSync={checkSync}
hasSyncedWallet={hasSyncedWallet}
getSyncIsPending={getSyncIsPending}
syncApplyIsPending={syncApplyIsPending}
onWalletViewLayout={this.onWalletViewLayout}
onPasswordChanged={this.onWalletPasswordChanged} />);
page = (
<WalletPage
checkSync={checkSync}
hasSyncedWallet={hasSyncedWallet}
getSyncIsPending={getSyncIsPending}
syncApplyIsPending={syncApplyIsPending}
onWalletViewLayout={this.onWalletViewLayout}
onPasswordChanged={this.onWalletPasswordChanged}
/>
);
break;
case Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT:
page = (<SkipAccountPage
onSkipAccountViewLayout={this.onSkipAccountViewLayout}
onSkipSwitchChanged={this.onSkipSwitchChanged} />);
page = (
<SkipAccountPage
onSkipAccountViewLayout={this.onSkipAccountViewLayout}
onSkipSwitchChanged={this.onSkipSwitchChanged}
/>
);
break;
}
return (
<View style={firstRunStyle.screenContainer}>
{page}
{this.state.currentPage && this.state.showBottomContainer &&
<View style={firstRunStyle.bottomContainer}>
{emailNewPending &&
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />}
{this.state.currentPage && this.state.showBottomContainer && (
<View style={firstRunStyle.bottomContainer}>
{emailNewPending && (
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />
)}
<View style={firstRunStyle.buttonRow}>
{([Constants.FIRST_RUN_PAGE_WELCOME, Constants.FIRST_RUN_PAGE_WALLET].indexOf(this.state.currentPage) > -1) && <View />}
{(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) &&
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
<Text style={firstRunStyle.buttonText}>
« {Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ? 'Setup account' : 'Change email'}</Text>
</TouchableOpacity>}
{!emailNewPending && (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) &&
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
<Text style={firstRunStyle.smallLeftButtonText}>No, thanks »</Text>
</TouchableOpacity>}
<View style={firstRunStyle.buttonRow}>
{[Constants.FIRST_RUN_PAGE_WELCOME, Constants.FIRST_RUN_PAGE_WALLET].indexOf(this.state.currentPage) >
-1 && <View />}
{(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) && (
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
<Text style={firstRunStyle.buttonText}>
«{' '}
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage
? 'Setup account'
: 'Change email'}
</Text>
</TouchableOpacity>
)}
{!emailNewPending && Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage && (
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
<Text style={firstRunStyle.smallLeftButtonText}>No, thanks »</Text>
</TouchableOpacity>
)}
{!emailNewPending &&
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage &&
<Text style={firstRunStyle.smallButtonText}>Use LBRY »</Text>}
{!emailNewPending && (
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage && (
<Text style={firstRunStyle.smallButtonText}>Use LBRY »</Text>
)}
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage &&
<Text style={firstRunStyle.buttonText}>{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? 'Use LBRY' : 'Continue'} »</Text>}
</TouchableOpacity>}
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage && (
<Text style={firstRunStyle.buttonText}>
{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? 'Use LBRY' : 'Continue'} »
</Text>
)}
</TouchableOpacity>
)}
</View>
</View>
</View>}
)}
</View>
);
}

View file

@ -7,7 +7,10 @@ import PublishPage from './view';
const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(null, perform)(PublishPage);
export default connect(
null,
perform
)(PublishPage);

View file

@ -25,7 +25,7 @@ class PublishPage extends React.PureComponent {
NativeModules.Gallery.getVideos().then(videos => {
console.log(videos);
});
}
};
componentDidMount() {
this.onComponentFocused();
@ -46,7 +46,6 @@ class PublishPage extends React.PureComponent {
return (
<View style={publishStyle.container}>
<UriBar navigation={navigation} />
</View>
);
}

View file

@ -30,7 +30,10 @@ const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(select, perform)(RewardsPage);
export default connect(
select,
perform
)(RewardsPage);

View file

@ -1,12 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
ActivityIndicator,
NativeModules,
ScrollView,
Text,
View
} from 'react-native';
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
import Colors from 'styles/colors';
import Constants from 'constants';
import Link from 'component/link';
@ -25,7 +19,7 @@ class RewardsPage extends React.PureComponent {
isRewardApproved: false,
verifyRequestStarted: false,
revealVerification: true,
firstRewardClaimed: false
firstRewardClaimed: false,
};
scrollView = null;
@ -51,11 +45,11 @@ class RewardsPage extends React.PureComponent {
fetchRewards();
this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email),
isIdentityVerified: (user && user.is_identity_verified),
isRewardApproved: (user && user.is_reward_approved)
isEmailVerified: user && user.primary_email && user.has_verified_email,
isIdentityVerified: user && user.is_identity_verified,
isRewardApproved: user && user.is_reward_approved,
});
}
};
componentDidMount() {
this.onComponentFocused();
@ -83,9 +77,9 @@ class RewardsPage extends React.PureComponent {
if (user) {
// update other checks (if new user data has been retrieved)
this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email),
isIdentityVerified: (user && user.is_identity_verified),
isRewardApproved: (user && user.is_reward_approved)
isEmailVerified: user && user.primary_email && user.has_verified_email,
isIdentityVerified: user && user.is_identity_verified,
isRewardApproved: user && user.is_reward_approved,
});
}
@ -111,7 +105,13 @@ class RewardsPage extends React.PureComponent {
<View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Manual Reward Verification</Text>
<Text style={rewardStyle.text}>
You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.greenLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />.
You need to be manually verified before you can start claiming rewards. Please request to be verified on the{' '}
<Link
style={rewardStyle.greenLink}
href="https://discordapp.com/invite/Z3bERWA"
text="LBRY Discord server"
/>
.
</Text>
</View>
);
@ -122,7 +122,7 @@ class RewardsPage extends React.PureComponent {
renderUnclaimedRewards() {
const { claimed, fetching, rewards, user } = this.props;
const unclaimedRewards = (rewards && rewards.length) ? rewards : [];
const unclaimedRewards = rewards && rewards.length ? rewards : [];
if (fetching) {
return (
@ -142,11 +142,15 @@ class RewardsPage extends React.PureComponent {
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
return (
<View>
{unclaimedRewards.map(reward => <RewardCard key={reward.reward_type}
showVerification={this.showVerification}
canClaim={!isNotEligible}
reward={reward}
reward_type={reward.reward_type} />)}
{unclaimedRewards.map(reward => (
<RewardCard
key={reward.reward_type}
showVerification={this.showVerification}
canClaim={!isNotEligible}
reward={reward}
reward_type={reward.reward_type}
/>
))}
<CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} />
</View>
);
@ -158,7 +162,9 @@ class RewardsPage extends React.PureComponent {
const reversed = claimed.reverse();
return (
<View>
{reversed.map(reward => <RewardCard key={reward.transaction_id} reward={reward} />)}
{reversed.map(reward => (
<RewardCard key={reward.transaction_id} reward={reward} />
))}
</View>
);
}
@ -170,7 +176,7 @@ class RewardsPage extends React.PureComponent {
this.scrollView.scrollTo({ x: 0, y: 0, animated: true });
}
});
}
};
render() {
const { user, navigation } = this.props;
@ -178,18 +184,19 @@ class RewardsPage extends React.PureComponent {
return (
<View style={rewardStyle.container}>
<UriBar navigation={navigation} />
{(!this.state.isEmailVerified || !this.state.isRewardApproved) &&
<RewardEnrolment navigation={navigation} />}
{(!this.state.isEmailVerified || !this.state.isRewardApproved) && <RewardEnrolment navigation={navigation} />}
{(this.state.isEmailVerified && this.state.isRewardApproved) &&
{this.state.isEmailVerified && this.state.isRewardApproved && (
<ScrollView
ref={ref => this.scrollView = ref}
ref={ref => (this.scrollView = ref)}
keyboardShouldPersistTaps={'handled'}
style={rewardStyle.scrollContainer}
contentContainerStyle={rewardStyle.scrollContentContainer}>
contentContainerStyle={rewardStyle.scrollContentContainer}
>
{this.renderUnclaimedRewards()}
{this.renderClaimedRewards()}
</ScrollView>}
</ScrollView>
)}
</View>
);
}

View file

@ -6,14 +6,14 @@ import {
selectIsSearching,
selectSearchValue,
makeSelectQueryWithOptions,
selectSearchUrisByQuery
selectSearchUrisByQuery,
} from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import Constants from 'constants';
import SearchPage from './view';
const select = (state) => ({
const select = state => ({
currentRoute: selectCurrentRoute(state),
isSearching: selectIsSearching(state),
query: selectSearchValue(state),
@ -22,10 +22,13 @@ const select = (state) => ({
});
const perform = dispatch => ({
search: (query) => dispatch(doSearch(query, 25)),
search: query => dispatch(doSearch(query, 25)),
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SEARCH)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});
export default connect(select, perform)(SearchPage);
export default connect(
select,
perform
)(SearchPage);

Some files were not shown because too many files have changed in this diff Show more