Merge pull request #578 from lbryio/prettier

add prettier and eslint
This commit is contained in:
Akinwale Ariwodola 2019-06-12 16:39:35 +01:00 committed by GitHub
commit 89c653e613
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
148 changed files with 4736 additions and 2589 deletions

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", "version": "0.0.1",
"private": "true", "private": "true",
"scripts": { "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": { "dependencies": {
"base-64": "^0.1.0", "base-64": "^0.1.0",
@ -42,6 +44,17 @@
"babel-preset-react-native": "5.0.2", "babel-preset-react-native": "5.0.2",
"babel-preset-stage-2": "^6.18.0", "babel-preset-stage-2": "^6.18.0",
"babel-plugin-module-resolver": "^3.1.1", "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

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

View file

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

View file

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

View file

@ -1,4 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from './view'; 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 { export default class Button extends React.PureComponent {
render() { render() {
const { const { disabled, style, text, icon, iconColor, solid, theme, onPress, onLayout } = this.props;
disabled,
style,
text,
icon,
iconColor,
solid,
theme,
onPress,
onLayout
} = this.props;
let styles = [buttonStyle.button, buttonStyle.row]; let styles = [buttonStyle.button, buttonStyle.row];
if (style) { if (style) {
@ -43,16 +33,25 @@ export default class Button extends React.PureComponent {
textStyles.push(buttonStyle.textLight); 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) { 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 ( return (
<TouchableOpacity disabled={disabled} style={styles} onPress={onPress} onLayout={onLayout}> <TouchableOpacity disabled={disabled} style={styles} onPress={onPress} onLayout={onLayout}>
{icon && renderIcon} {icon && renderIcon}
{text && (text.trim().length > 0) && <Text style={textStyles}>{text}</Text>} {text && text.trim().length > 0 && <Text style={textStyles}>{text}</Text>}
</TouchableOpacity> </TouchableOpacity>
); );
} }
}; }

View file

@ -1,4 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import CategoryList from './view'; 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} initialNumToRender={3}
maxToRenderPerBatch={3} maxToRenderPerBatch={3}
removeClippedSubviews={true} removeClippedSubviews={true}
renderItem={ ({item}) => ( renderItem={({ item }) => (
<FileItem <FileItem
style={discoverStyle.fileItem} style={discoverStyle.fileItem}
mediaStyle={discoverStyle.fileItemMedia} mediaStyle={discoverStyle.fileItemMedia}
@ -24,9 +24,9 @@ class CategoryList extends React.PureComponent {
uri={normalizeURI(item)} uri={normalizeURI(item)}
navigation={navigation} navigation={navigation}
showDetails={true} showDetails={true}
compactView={false} /> compactView={false}
) />
} )}
horizontal={true} horizontal={true}
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
data={categoryMap[category]} data={categoryMap[category]}

View file

@ -5,7 +5,7 @@ import {
doClaimRewardClearError, doClaimRewardClearError,
makeSelectClaimRewardError, makeSelectClaimRewardError,
makeSelectIsRewardClaimPending, makeSelectIsRewardClaimPending,
rewards as REWARD_TYPES rewards as REWARD_TYPES,
} from 'lbryinc'; } from 'lbryinc';
import CustomRewardCard from './view'; import CustomRewardCard from './view';
@ -20,7 +20,10 @@ const perform = dispatch => ({
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)), claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
clearError: reward => dispatch(doClaimRewardClearError(reward)), clearError: reward => dispatch(doClaimRewardClearError(reward)),
notify: data => dispatch(doToast(data)), 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> { class CustomRewardCard extends React.PureComponent<Props> {
state = { state = {
claimStarted: false, claimStarted: false,
rewardCode: '' rewardCode: '',
}; };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -49,31 +49,39 @@ class CustomRewardCard extends React.PureComponent<Props> {
this.setState({ claimStarted: true }, () => { this.setState({ claimStarted: true }, () => {
submitRewardCode(rewardCode); submitRewardCode(rewardCode);
}); });
} };
render() { render() {
const { canClaim, rewardIsPending } = this.props; const { canClaim, rewardIsPending } = this.props;
return ( return (
<View style={[rewardStyle.rewardCard, rewardStyle.row]} > <View style={[rewardStyle.rewardCard, rewardStyle.row]}>
<View style={rewardStyle.leftCol}> <View style={rewardStyle.leftCol}>
{rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />} {rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
</View> </View>
<View style={rewardStyle.midCol}> <View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>Custom Code</Text> <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> <View>
<TextInput style={rewardStyle.customCodeInput} <TextInput
placeholder={"0123abc"} style={rewardStyle.customCodeInput}
onChangeText={text => this.setState({ rewardCode: text })} placeholder={'0123abc'}
value={this.state.rewardCode} /> onChangeText={text => this.setState({ rewardCode: text })}
<Button style={rewardStyle.redeemButton} value={this.state.rewardCode}
text={"Redeem"} />
disabled={(!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending)} <Button
onPress={() => { style={rewardStyle.redeemButton}
if (!rewardIsPending) { this.onClaimPress(); } text={'Redeem'}
}} /> disabled={!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending}
onPress={() => {
if (!rewardIsPending) {
this.onClaimPress();
}
}}
/>
</View> </View>
</View> </View>
<View style={rewardStyle.rightCol}> <View style={rewardStyle.rightCol}>
@ -83,6 +91,6 @@ class CustomRewardCard extends React.PureComponent<Props> {
</View> </View>
); );
} }
}; }
export default CustomRewardCard; 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 const locale = 'en-US'; // default to en-US until we get a working i18n module for RN
if (timeAgo) { 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 // TODO: formatOptions not working as expected in RN
@ -38,13 +42,9 @@ class DateTime extends React.PureComponent<Props> {
return ( return (
<View style={style}> <View style={style}>
<Text style={textStyle}> <Text style={textStyle}>
{date && {date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && moment(date).format('MMMM D, YYYY')}
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) &&
moment(date).format('MMMM D, YYYY')}
{show === DateTime.SHOW_BOTH && ' '} {show === DateTime.SHOW_BOTH && ' '}
{date && {date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()}
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) &&
date.toLocaleTimeString()}
{!date && '...'} {!date && '...'}
</Text> </Text>
</View> </View>

View file

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

View file

@ -22,4 +22,7 @@ const perform = dispatch => ({
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)), 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; } = this.props;
if ((fileInfo && !fileInfo.stopped) || loading || downloading) { if ((fileInfo && !fileInfo.stopped) || loading || downloading) {
const progress = const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0,
fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...'; label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...';
return ( return (
<View style={[style, fileDownloadButtonStyle.container]}> <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> <Text style={fileDownloadButtonStyle.text}>{label}</Text>
</View> </View>
); );
@ -68,29 +69,35 @@ class FileDownloadButton extends React.PureComponent {
); );
} }
return ( return (
<Button icon={isPlayable ? 'play' : null} <Button
text={(isPlayable ? 'Play' : (isViewable ? 'View' : 'Download'))} icon={isPlayable ? 'play' : null}
onLayout={onButtonLayout} text={isPlayable ? 'Play' : isViewable ? 'View' : 'Download'}
style={[style, fileDownloadButtonStyle.container]} onPress={() => { onLayout={onButtonLayout}
if (NativeModules.Firebase) { style={[style, fileDownloadButtonStyle.container]}
NativeModules.Firebase.track('purchase_uri', { uri: uri }); onPress={() => {
} if (NativeModules.Firebase) {
purchaseUri(uri, costInfo, !isPlayable); NativeModules.Firebase.track('purchase_uri', { uri: uri });
if (NativeModules.UtilityModule) { }
NativeModules.UtilityModule.checkDownloads(); purchaseUri(uri, costInfo, !isPlayable);
} if (NativeModules.UtilityModule) {
if (isPlayable && onPlay) { NativeModules.UtilityModule.checkDownloads();
this.props.onPlay(); }
} if (isPlayable && onPlay) {
if (isViewable && onView) { this.props.onPlay();
this.props.onView(); }
} if (isViewable && onView) {
}} /> this.props.onView();
}
}}
/>
); );
} else if (fileInfo && fileInfo.download_path) { } else if (fileInfo && fileInfo.download_path) {
return ( return (
<TouchableOpacity onLayout={onButtonLayout} <TouchableOpacity
style={[style, fileDownloadButtonStyle.container]} onPress={openFile}> onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={openFile}
>
<Text style={fileDownloadButtonStyle.text}>{isViewable ? 'View' : 'Open'}</Text> <Text style={fileDownloadButtonStyle.text}>{isViewable ? 'View' : 'Open'}</Text>
</TouchableOpacity> </TouchableOpacity>
); );

View file

@ -7,7 +7,7 @@ import {
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectIsUriResolving, makeSelectIsUriResolving,
makeSelectClaimIsNsfw makeSelectClaimIsNsfw,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectRewardContentClaimIds } from 'lbryinc'; import { selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
@ -29,4 +29,7 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), 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 }); NativeModules.Firebase.track('explore_click', { uri: normalizedUri });
} }
navigateToUri(navigation, normalizedUri); navigateToUri(navigation, normalizedUri);
} };
render() { render() {
const { const {
@ -56,46 +56,73 @@ class FileItem extends React.PureComponent {
navigation, navigation,
showDetails, showDetails,
compactView, compactView,
titleBeforeThumbnail titleBeforeThumbnail,
} = this.props; } = this.props;
const uri = normalizeURI(this.props.uri); const uri = normalizeURI(this.props.uri);
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const channelName = claim ? claim.channel_name : null; 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 fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
const height = claim ? claim.height : null; const height = claim ? claim.height : null;
return ( return (
<View style={style}> <View style={style}>
<TouchableOpacity style={discoverStyle.container} onPress={this.navigateToFileUri}> <TouchableOpacity style={discoverStyle.container} onPress={this.navigateToFileUri}>
{!compactView && titleBeforeThumbnail && <Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>} {!compactView && titleBeforeThumbnail && (
<FileItemMedia title={title} <Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
thumbnail={thumbnail} {title}
blurRadius={obscureNsfw ? 15 : 0} </Text>
resizeMode="cover" )}
isResolvingUri={isResolvingUri} <FileItemMedia
style={mediaStyle} /> title={title}
thumbnail={thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
isResolvingUri={isResolvingUri}
style={mediaStyle}
/>
{(!compactView && fileInfo && fileInfo.completed && fileInfo.download_path) && {!compactView && fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon style={discoverStyle.downloadedIcon} solid={true} color={Colors.NextLbryGreen} name={"folder"} size={16} />} <Icon
{(!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path)) && style={discoverStyle.downloadedIcon}
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />} solid={true}
{!compactView && <View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}> color={Colors.NextLbryGreen}
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text> name={'folder'}
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={14} />} size={16}
</View>} />
{(!compactView && showDetails) && )}
<View style={discoverStyle.detailsRow}> {!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
{channelName && <FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
<Link style={discoverStyle.channelName} text={channelName} onPress={() => { )}
navigateToUri(navigation, normalizeURI(fullChannelUri)); {!compactView && (
}} />} <View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} /> <Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
</View>} {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> </TouchableOpacity>
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />} {obscureNsfw && (
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
)}
</View> </View>
); );
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,4 +3,7 @@ import NsfwOverlay from './view';
const perform = dispatch => ({}); 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() { render() {
return ( return (
<TouchableOpacity style={discoverStyle.overlay} activeOpacity={0.95} onPress={this.props.onPress}> <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> </TouchableOpacity>
) );
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import RewardEnrolment from './view';
const select = state => ({ const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state), unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state), fetching: selectFetchingRewards(state),
user: selectUser(state) user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -16,4 +16,7 @@ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), 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; const { navigation, setClientSetting } = this.props;
setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true); setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true);
navigation.navigate({ routeName: 'DiscoverStack' }); navigation.navigate({ routeName: 'DiscoverStack' });
} };
onEnrollPressed = () => { onEnrollPressed = () => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false }}); navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false } });
} };
render() { render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props; const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
@ -31,20 +31,20 @@ class RewardEnrolment extends React.Component {
<View style={rewardStyle.enrollContainer} onPress> <View style={rewardStyle.enrollContainer} onPress>
<View style={rewardStyle.summaryRow}> <View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} /> <Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}> <Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
{unclaimedRewardAmount} unclaimed credits
</Text>
</View> </View>
<View style={rewardStyle.onboarding}> <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>
<View style={rewardStyle.buttonRow}> <View style={rewardStyle.buttonRow}>
<Link style={rewardStyle.notInterestedLink} text={"Not interested"} onPress={this.onNotInterestedPressed} /> <Link style={rewardStyle.notInterestedLink} text={'Not interested'} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={"light"} text={"Enroll"} onPress={this.onEnrollPressed} /> <Button style={rewardStyle.enrollButton} theme={'light'} text={'Enroll'} onPress={this.onEnrollPressed} />
</View> </View>
</View> </View>
); );
} }

View file

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

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { NativeModules } from 'react-native'; import { NativeModules } from 'react-native';
import { doSearch, doUpdateSearchQuery } from 'lbry-redux'; import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
import SearchInput from './view'; import SearchInput from './view';
const perform = dispatch => ({ const perform = dispatch => ({
@ -10,7 +10,10 @@ const perform = dispatch => ({
} }
return dispatch(doSearch(search)); 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

@ -5,7 +5,7 @@ class SearchInput extends React.PureComponent {
static INPUT_TIMEOUT = 500; static INPUT_TIMEOUT = 500;
state = { state = {
changeTextTimeout: -1 changeTextTimeout: -1,
}; };
handleChangeText = text => { handleChangeText = text => {
@ -21,7 +21,7 @@ class SearchInput extends React.PureComponent {
search(text); search(text);
}, SearchInput.INPUT_TIMEOUT); }, SearchInput.INPUT_TIMEOUT);
this.setState({ changeTextTimeout: timeout }); this.setState({ changeTextTimeout: timeout });
} };
render() { render() {
const { style, value } = this.props; const { style, value } = this.props;
@ -32,7 +32,8 @@ class SearchInput extends React.PureComponent {
placeholder="Search" placeholder="Search"
underlineColorAndroid="transparent" underlineColorAndroid="transparent"
value={value} 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 SearchRightHeaderIcon from './view';
import { ACTIONS } from 'lbry-redux'; import { ACTIONS } from 'lbry-redux';
const perform = dispatch => ({ const perform = dispatch => ({
clearQuery: () => dispatch({ clearQuery: () =>
type: ACTIONS.HISTORY_NAVIGATE 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 React from 'react';
import { NavigationActions } from 'react-navigation'; 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 { class SearchRightHeaderIcon extends React.PureComponent {
clearAndGoBack() { clearAndGoBack() {
const { navigation } = this.props; const { navigation } = this.props;
this.props.clearQuery(); this.props.clearQuery();
navigation.dispatch(NavigationActions.back()) navigation.dispatch(NavigationActions.back());
} }
render() { render() {

View file

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

View file

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

View file

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

View file

@ -14,11 +14,11 @@ class SubscribeNotificationButton extends React.PureComponent {
doToast, doToast,
enabledChannelNotifications, enabledChannelNotifications,
isSubscribed, isSubscribed,
style style,
} = this.props; } = this.props;
if (!isSubscribed) { if (!isSubscribed) {
return null; return null;
} }
let styles = []; let styles = [];
@ -36,8 +36,8 @@ class SubscribeNotificationButton extends React.PureComponent {
return ( return (
<Button <Button
style={styles} style={styles}
theme={"light"} theme={'light'}
icon={shouldNotify ? "bell-slash" : "bell"} icon={shouldNotify ? 'bell-slash' : 'bell'}
solid={true} solid={true}
onPress={() => { onPress={() => {
if (shouldNotify) { if (shouldNotify) {
@ -47,7 +47,8 @@ class SubscribeNotificationButton extends React.PureComponent {
doChannelSubscriptionEnableNotifications(name); doChannelSubscriptionEnableNotifications(name);
doToast({ message: 'You will receive all notifications for new content.' }); 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 { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = claim;
const uriParams = {}; const uriParams = {};
// This is unfortunate // This is unfortunate
// https://github.com/lbryio/lbry/issues/1159 // https://github.com/lbryio/lbry/issues/1159
const name = claimName || claimNameDownloaded; const name = claimName || claimNameDownloaded;
uriParams.contentName = name; uriParams.contentName = name;
uriParams.claimId = claimId; uriParams.claimId = claimId;
const uri = buildURI(uriParams); const uri = buildURI(uriParams);
return uri; return uri;
} };
render() { render() {
const { categoryLink, fetching, obscureNsfw, claims, navigation } = this.props; const { categoryLink, fetching, obscureNsfw, claims, navigation } = this.props;
@ -46,23 +46,26 @@ class SuggestedSubscriptionItem extends React.PureComponent {
style={subscriptionsStyle.compactMainFileItem} style={subscriptionsStyle.compactMainFileItem}
mediaStyle={subscriptionsStyle.fileItemMedia} mediaStyle={subscriptionsStyle.fileItemMedia}
uri={this.uriForClaim(claims[0])} uri={this.uriForClaim(claims[0])}
navigation={navigation} /> navigation={navigation}
{(claims.length > 1) && />
<FlatList style={subscriptionsStyle.compactItems} {claims.length > 1 && (
horizontal={true} <FlatList
renderItem={ ({item}) => ( style={subscriptionsStyle.compactItems}
horizontal={true}
renderItem={({ item }) => (
<FileItem <FileItem
style={subscriptionsStyle.compactFileItem} style={subscriptionsStyle.compactFileItem}
mediaStyle={subscriptionsStyle.compactFileItemMedia} mediaStyle={subscriptionsStyle.compactFileItemMedia}
key={item} key={item}
uri={normalizeURI(item)} uri={normalizeURI(item)}
navigation={navigation} navigation={navigation}
compactView={true} /> compactView={true}
) />
} )}
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))} data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
keyExtractor={(item, index) => item} keyExtractor={(item, index) => item}
/>} />
)}
</View> </View>
); );
} }

View file

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

View file

@ -8,4 +8,7 @@ const select = state => ({
myClaims: selectAllMyClaimsByOutpoint(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 <Link
style={transactionListStyle.link} style={transactionListStyle.link}
onPress={() => navigateToUri(navigation, buildURI({ claimName: name, claimId }))} onPress={() => navigateToUri(navigation, buildURI({ claimName: name, claimId }))}
text={name} /> text={name}
/>
)} )}
</View> </View>
<View style={transactionListStyle.col}> <View style={transactionListStyle.col}>
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>{formatCredits(amount, 8)}</Text> <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> </View>
<View style={transactionListStyle.row}> <View style={transactionListStyle.row}>
<View style={transactionListStyle.col}> <View style={transactionListStyle.col}>
<Link style={transactionListStyle.smallLink} <Link
text={txid.substring(0, 8)} style={transactionListStyle.smallLink}
href={`https://explorer.lbry.com/tx/${txid}`} text={txid.substring(0, 8)}
error={'The transaction URL could not be opened'} /> href={`https://explorer.lbry.com/tx/${txid}`}
error={'The transaction URL could not be opened'}
/>
</View> </View>
<View style={transactionListStyle.col}> <View style={transactionListStyle.col}>
{date ? ( {date ? (

View file

@ -17,4 +17,7 @@ const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()), 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.transactionsCard}>
<View style={[walletStyle.row, walletStyle.transactionsHeader]}> <View style={[walletStyle.row, walletStyle.transactionsHeader]}>
<Text style={walletStyle.transactionsTitle}>Recent Transactions</Text> <Text style={walletStyle.transactionsTitle}>Recent Transactions</Text>
<Link style={walletStyle.link} <Link style={walletStyle.link} navigation={navigation} text={'View All'} href={'#TransactionHistory'} />
navigation={navigation}
text={'View All'}
href={'#TransactionHistory'} />
</View> </View>
{fetchingTransactions && ( {fetchingTransactions && <Text style={walletStyle.infoText}>Fetching transactions...</Text>}
<Text style={walletStyle.infoText}>Fetching transactions...</Text>
)}
{!fetchingTransactions && ( {!fetchingTransactions && (
<TransactionList <TransactionList
navigation={navigation} navigation={navigation}

View file

@ -3,7 +3,7 @@ import {
doUpdateSearchQuery, doUpdateSearchQuery,
selectSearchState as selectSearch, selectSearchState as selectSearch,
selectSearchValue, selectSearchValue,
selectSearchSuggestions selectSearchSuggestions,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectCurrentRoute } from 'redux/selectors/drawer'; import { selectCurrentRoute } from 'redux/selectors/drawer';
import UriBar from './view'; import UriBar from './view';
@ -15,7 +15,7 @@ const select = state => {
...searchState, ...searchState,
query: selectSearchValue(state), query: selectSearchValue(state),
currentRoute: selectCurrentRoute(state), currentRoute: selectCurrentRoute(state),
suggestions: selectSearchSuggestions(state) suggestions: selectSearchSuggestions(state),
}; };
}; };
@ -23,4 +23,7 @@ const perform = dispatch => ({
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)), 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; let icon;
switch (type) { switch (type) {
case SEARCH_TYPES.CHANNEL: case SEARCH_TYPES.CHANNEL:
icon = <Icon name="at" size={18} /> icon = <Icon name="at" size={18} />;
break; break;
case SEARCH_TYPES.SEARCH: case SEARCH_TYPES.SEARCH:
icon = <Icon name="search" size={18} /> icon = <Icon name="search" size={18} />;
break; break;
case SEARCH_TYPES.FILE: case SEARCH_TYPES.FILE:
default: default:
icon = <Icon name="file" size={18} /> icon = <Icon name="file" size={18} />;
break; break;
} }
@ -30,7 +30,9 @@ class UriBarItem extends React.PureComponent {
<TouchableOpacity style={uriBarStyle.item} onPress={onPress}> <TouchableOpacity style={uriBarStyle.item} onPress={onPress}>
{icon} {icon}
<View style={uriBarStyle.itemContent}> <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}> <Text style={uriBarStyle.itemDesc} numberOfLines={1}>
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`} {type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`} {type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
@ -38,7 +40,7 @@ class UriBarItem extends React.PureComponent {
</Text> </Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>
) );
} }
} }

View file

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

View file

@ -1,10 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { doCheckAddressIsMine, doGetNewAddress, selectReceiveAddress, selectGettingNewAddress } from 'lbry-redux';
doCheckAddressIsMine,
doGetNewAddress,
selectReceiveAddress,
selectGettingNewAddress,
} from 'lbry-redux';
import WalletAddress from './view'; import WalletAddress from './view';
const select = state => ({ const select = state => ({
@ -17,4 +12,7 @@ const perform = dispatch => ({
getNewAddress: () => dispatch(doGetNewAddress()), 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 ( return (
<View style={walletStyle.card}> <View style={walletStyle.card}>
<Text style={walletStyle.title}>Receive Credits</Text> <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} /> <Address address={receiveAddress} style={walletStyle.bottomMarginSmall} />
<Button style={[walletStyle.button, walletStyle.bottomMarginLarge]} <Button
icon={'sync'} style={[walletStyle.button, walletStyle.bottomMarginLarge]}
text={'Get new address'} icon={'sync'}
onPress={getNewAddress} text={'Get new address'}
disabled={gettingNewAddress} onPress={getNewAddress}
/> disabled={gettingNewAddress}
/>
<Text style={walletStyle.smallText}> <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> </Text>
</View> </View>
); );

View file

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

View file

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

View file

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

View file

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

View file

@ -1,72 +1,72 @@
const Constants = { const Constants = {
FIRST_RUN_PAGE_WELCOME: "welcome", FIRST_RUN_PAGE_WELCOME: 'welcome',
FIRST_RUN_PAGE_EMAIL_COLLECT: "email-collect", FIRST_RUN_PAGE_EMAIL_COLLECT: 'email-collect',
FIRST_RUN_PAGE_EMAIL_VERIFY:"email-verify", FIRST_RUN_PAGE_EMAIL_VERIFY: 'email-verify',
FIRST_RUN_PAGE_WALLET: "wallet", FIRST_RUN_PAGE_WALLET: 'wallet',
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account", FIRST_RUN_PAGE_SKIP_ACCOUNT: 'skip-account',
VERIFY_PAGE_EMAIL: "email-verify", VERIFY_PAGE_EMAIL: 'email-verify',
VERIFY_PAGE_PHONE_NUMBER: "phone-number-verify", VERIFY_PAGE_PHONE_NUMBER: 'phone-number-verify',
PHASE_COLLECTION: "collection", PHASE_COLLECTION: 'collection',
PHASE_VERIFICATION: "verification", PHASE_VERIFICATION: 'verification',
CONTENT_TAB: "content", CONTENT_TAB: 'content',
ABOUT_TAB: "about", ABOUT_TAB: 'about',
KEY_FIRST_RUN_EMAIL: "firstRunEmail", KEY_FIRST_RUN_EMAIL: 'firstRunEmail',
KEY_FIRST_RUN_PASSWORD: "firstRunPassword", KEY_FIRST_RUN_PASSWORD: 'firstRunPassword',
KEY_FIRST_USER_AUTH: "firstUserAuth", KEY_FIRST_USER_AUTH: 'firstUserAuth',
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail", KEY_SHOULD_VERIFY_EMAIL: 'shouldVerifyEmail',
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending", KEY_EMAIL_VERIFY_PENDING: 'emailVerifyPending',
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks", SETTING_ALPHA_UNDERSTANDS_RISKS: 'alphaUnderstandRisks',
SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode", SETTING_SUBSCRIPTIONS_VIEW_MODE: 'subscriptionsViewMode',
SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown", SETTING_RATING_REMINDER_LAST_SHOWN: 'ratingReminderLastShown',
SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled", SETTING_RATING_REMINDER_DISABLED: 'ratingReminderDisabled',
SETTING_BACKUP_DISMISSED: "backupDismissed", SETTING_BACKUP_DISMISSED: 'backupDismissed',
SETTING_REWARDS_NOT_INTERESTED: "rewardsNotInterested", SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
SETTING_DEVICE_WALLET_SYNCED: "deviceWalletSynced", SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS", ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED", ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
ACTION_PUSH_DRAWER_STACK: "PUSH_DRAWER_STACK", ACTION_PUSH_DRAWER_STACK: 'PUSH_DRAWER_STACK',
ACTION_POP_DRAWER_STACK: "POP_DRAWER_STACK", ACTION_POP_DRAWER_STACK: 'POP_DRAWER_STACK',
ACTION_SET_PLAYER_VISIBLE: "SET_PLAYER_VISIBLE", ACTION_SET_PLAYER_VISIBLE: 'SET_PLAYER_VISIBLE',
ACTION_REACT_NAVIGATION_RESET: "Navigation/RESET", ACTION_REACT_NAVIGATION_RESET: 'Navigation/RESET',
ACTION_REACT_NAVIGATION_NAVIGATE: "Navigation/NAVIGATE", ACTION_REACT_NAVIGATION_NAVIGATE: 'Navigation/NAVIGATE',
ACTION_REACT_NAVIGATION_REPLACE: "Navigation/REPLACE", ACTION_REACT_NAVIGATION_REPLACE: 'Navigation/REPLACE',
PAGE_REWARDS: "rewards", PAGE_REWARDS: 'rewards',
PAGE_SETTINGS: "settings", PAGE_SETTINGS: 'settings',
PAGE_TRENDING: "trending", PAGE_TRENDING: 'trending',
PAGE_WALLET: "wallet", PAGE_WALLET: 'wallet',
DRAWER_ROUTE_DISCOVER: "Discover", DRAWER_ROUTE_DISCOVER: 'Discover',
DRAWER_ROUTE_TRENDING: "Trending", DRAWER_ROUTE_TRENDING: 'Trending',
DRAWER_ROUTE_SUBSCRIPTIONS: "Subscriptions", DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
DRAWER_ROUTE_MY_LBRY: "Downloads", DRAWER_ROUTE_MY_LBRY: 'Downloads',
DRAWER_ROUTE_REWARDS: "Rewards", DRAWER_ROUTE_REWARDS: 'Rewards',
DRAWER_ROUTE_WALLET: "Wallet", DRAWER_ROUTE_WALLET: 'Wallet',
DRAWER_ROUTE_SETTINGS: "Settings", DRAWER_ROUTE_SETTINGS: 'Settings',
DRAWER_ROUTE_ABOUT: "About", DRAWER_ROUTE_ABOUT: 'About',
DRAWER_ROUTE_SEARCH: "Search", DRAWER_ROUTE_SEARCH: 'Search',
DRAWER_ROUTE_TRANSACTION_HISTORY: "TransactionHistory", DRAWER_ROUTE_TRANSACTION_HISTORY: 'TransactionHistory',
FULL_ROUTE_NAME_DISCOVER: "DiscoverStack", FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
FULL_ROUTE_NAME_TRENDING: "TrendingStack", FULL_ROUTE_NAME_TRENDING: 'TrendingStack',
FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: "MySubscriptionsStack", FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: 'MySubscriptionsStack',
FULL_ROUTE_NAME_WALLET: "WalletStack", FULL_ROUTE_NAME_WALLET: 'WalletStack',
FULL_ROUTE_NAME_MY_LBRY: "MyLBRYStack", FULL_ROUTE_NAME_MY_LBRY: 'MyLBRYStack',
ROUTE_FILE: "File", ROUTE_FILE: 'File',
SUBSCRIPTIONS_VIEW_ALL: "view_all", SUBSCRIPTIONS_VIEW_ALL: 'view_all',
SUBSCRIPTIONS_VIEW_LATEST_FIRST: "view_latest_first", 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) RATING_REMINDER_INTERVAL: 604800, // 7 days (7 * 24 * 3600s)
}; };
@ -82,5 +82,5 @@ export const DrawerRoutes = [
Constants.DRAWER_ROUTE_SETTINGS, Constants.DRAWER_ROUTE_SETTINGS,
Constants.DRAWER_ROUTE_ABOUT, Constants.DRAWER_ROUTE_ABOUT,
Constants.DRAWER_ROUTE_SEARCH, 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 React from 'react';
import { setJSExceptionHandler } from 'react-native-exception-handler'; import { setJSExceptionHandler } from 'react-native-exception-handler';
import { Provider, connect } from 'react-redux'; import { Provider, connect } from 'react-redux';
import { import { AppRegistry, Text, View, NativeModules } from 'react-native';
AppRegistry,
Text,
View,
NativeModules
} from 'react-native';
import { import {
Lbry, Lbry,
claimsReducer, claimsReducer,
@ -15,7 +10,7 @@ import {
fileInfoReducer, fileInfoReducer,
notificationsReducer, notificationsReducer,
searchReducer, searchReducer,
walletReducer walletReducer,
} from 'lbry-redux'; } from 'lbry-redux';
import { import {
authReducer, authReducer,
@ -25,10 +20,14 @@ import {
rewardsReducer, rewardsReducer,
subscriptionsReducer, subscriptionsReducer,
syncReducer, syncReducer,
userReducer userReducer,
} from 'lbryinc'; } from 'lbryinc';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux'; 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 { persistStore, autoRehydrate } from 'redux-persist';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import FilesystemStorage from 'redux-persist-filesystem-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 settingsReducer from 'redux/reducers/settings';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => { const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) { 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); setJSExceptionHandler(globalExceptionHandler, true);
@ -97,7 +95,7 @@ const reducers = combineReducers({
subscriptions: subscriptionsReducer, subscriptions: subscriptionsReducer,
sync: syncReducer, sync: syncReducer,
user: userReducer, user: userReducer,
wallet: walletReducer wallet: walletReducer,
}); });
const bulkThunk = createBulkThunkMiddleware(); const bulkThunk = createBulkThunkMiddleware();
@ -109,10 +107,7 @@ const composeEnhancers = compose;
const store = createStore( const store = createStore(
enableBatching(reducers), enableBatching(reducers),
{}, // initial state, {}, // initial state,
composeEnhancers( composeEnhancers(autoRehydrate(), applyMiddleware(...middleware))
autoRehydrate(),
applyMiddleware(...middleware)
)
); );
window.store = store; window.store = store;
@ -130,7 +125,7 @@ const persistOptions = {
// read the data // read the data
transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor], transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
debounce: 10000, debounce: 10000,
storage: FilesystemStorage storage: FilesystemStorage,
}; };
persistStore(store, persistOptions, err => { persistStore(store, persistOptions, err => {
@ -140,7 +135,7 @@ persistStore(store, persistOptions, err => {
}); });
// TODO: Find i18n module that is compatible with react-native // TODO: Find i18n module that is compatible with react-native
global.__ = (str) => str; global.__ = str => str;
class LBRYApp extends React.Component { class LBRYApp extends React.Component {
render() { render() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ import {
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import FirstRun from './view'; import FirstRun from './view';
const select = (state) => ({ const select = state => ({
authenticating: selectAuthenticationIsPending(state), authenticating: selectAuthenticationIsPending(state),
authToken: selectAuthToken(state), authToken: selectAuthToken(state),
emailToVerify: selectEmailToVerify(state), emailToVerify: selectEmailToVerify(state),
@ -48,7 +48,10 @@ const perform = dispatch => ({
checkSync: () => dispatch(doCheckSync()), checkSync: () => dispatch(doCheckSync()),
setDefaultAccount: () => dispatch(doSetDefaultAccount()), setDefaultAccount: () => dispatch(doSetDefaultAccount()),
notify: data => dispatch(doToast(data)), 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 React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { import { NativeModules, Platform, Text, TextInput, View } from 'react-native';
NativeModules,
Platform,
Text,
TextInput,
View
} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants';
@ -16,7 +10,7 @@ class EmailCollectPage extends React.PureComponent {
state = { state = {
email: null, email: null,
placeholder: 'you@example.com', placeholder: 'you@example.com',
verifying: true verifying: true,
}; };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -34,7 +28,7 @@ class EmailCollectPage extends React.PureComponent {
} }
} }
handleChangeText = (text) => { handleChangeText = text => {
// save the value to the state email // save the value to the state email
const { onEmailChanged } = this.props; const { onEmailChanged } = this.props;
this.setState({ email: text }); this.setState({ email: text });
@ -43,7 +37,7 @@ class EmailCollectPage extends React.PureComponent {
if (onEmailChanged) { if (onEmailChanged) {
onEmailChanged(text); onEmailChanged(text);
} }
} };
render() { render() {
const { onEmailViewLayout } = this.props; const { onEmailViewLayout } = this.props;
@ -51,33 +45,34 @@ class EmailCollectPage extends React.PureComponent {
const content = ( const content = (
<View onLayout={onEmailViewLayout}> <View onLayout={onEmailViewLayout}>
<Text style={firstRunStyle.title}>Setup account</Text> <Text style={firstRunStyle.title}>Setup account</Text>
<TextInput style={firstRunStyle.emailInput} <TextInput
placeholder={this.state.placeholder} style={firstRunStyle.emailInput}
underlineColorAndroid="transparent" placeholder={this.state.placeholder}
selectionColor={Colors.NextLbryGreen} underlineColorAndroid="transparent"
value={this.state.email} selectionColor={Colors.NextLbryGreen}
onChangeText={text => this.handleChangeText(text)} value={this.state.email}
onFocus={() => { onChangeText={text => this.handleChangeText(text)}
if (!this.state.email || this.state.email.length === 0) { onFocus={() => {
this.setState({ placeholder: '' }); if (!this.state.email || this.state.email.length === 0) {
} this.setState({ placeholder: '' });
}} }
onBlur={() => { }}
if (!this.state.email || this.state.email.length === 0) { onBlur={() => {
this.setState({ placeholder: 'you@example.com' }); 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> <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> </View>
); );
return ( return <View style={firstRunStyle.container}>{content}</View>;
<View style={firstRunStyle.container}>
{content}
</View>
);
} }
} }

View file

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

View file

@ -9,7 +9,7 @@ import {
Switch, Switch,
Text, Text,
TextInput, TextInput,
View View,
} from 'react-native'; } from 'react-native';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants';
@ -18,7 +18,7 @@ import firstRunStyle from 'styles/firstRun';
class SkipAccountPage extends React.PureComponent { class SkipAccountPage extends React.PureComponent {
state = { state = {
confirmed: false confirmed: false,
}; };
render() { render() {
@ -30,22 +30,29 @@ class SkipAccountPage extends React.PureComponent {
<Icon name="exclamation-triangle" style={firstRunStyle.titleIcon} size={32} color={Colors.White} /> <Icon name="exclamation-triangle" style={firstRunStyle.titleIcon} size={32} color={Colors.White} />
<Text style={firstRunStyle.title}>Are you sure?</Text> <Text style={firstRunStyle.title}>Are you sure?</Text>
</View> </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.row, firstRunStyle.confirmContainer]}>
<View style={firstRunStyle.rowSwitch}> <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> </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>
</View> </View>
); );
return ( return <View style={firstRunStyle.container}>{content}</View>;
<View style={firstRunStyle.container}>
{content}
</View>
);
} }
} }

View file

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

View file

@ -1,12 +1,6 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { import { ActivityIndicator, NativeModules, Platform, Text, View } from 'react-native';
ActivityIndicator,
NativeModules,
Platform,
Text,
View
} from 'react-native';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants';
@ -53,25 +47,27 @@ class WelcomePage extends React.PureComponent {
const { authenticate } = this.props; const { authenticate } = this.props;
this.setState({ authenticationStarted: true, authenticationFailed: false }); this.setState({ authenticationStarted: true, authenticationFailed: false });
NativeModules.VersionInfo.getAppVersion().then(appVersion => { NativeModules.VersionInfo.getAppVersion().then(appVersion => {
Lbry.status().then(info => { Lbry.status()
this.setState({ sdkStarted: true }); .then(info => {
this.setState({ sdkStarted: true });
authenticate(appVersion, Platform.OS); authenticate(appVersion, Platform.OS);
}).catch(error => { })
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) { .catch(error => {
this.setState({ authenticationFailed: true }); if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
this.setState({ authenticationFailed: true });
// sdk_start_failed // sdk_start_failed
NativeModules.Firebase.track('sdk_start_failed', null); NativeModules.Firebase.track('sdk_start_failed', null);
} else { } else {
setTimeout(() => { setTimeout(() => {
this.startAuthenticating(); this.startAuthenticating();
this.setState({ statusTries: this.state.statusTries + 1 }); this.setState({ statusTries: this.state.statusTries + 1 });
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds) }, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
} }
}); });
}); });
} };
render() { render() {
const { authenticating, authToken, onWelcomePageLayout } = this.props; const { authenticating, authToken, onWelcomePageLayout } = this.props;
@ -81,7 +77,10 @@ class WelcomePage extends React.PureComponent {
// Ask the user to try again // Ask the user to try again
content = ( content = (
<View> <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> </View>
); );
} else if (!authToken || authenticating) { } else if (!authToken || authenticating) {
@ -95,16 +94,15 @@ class WelcomePage extends React.PureComponent {
content = ( content = (
<View onLayout={onWelcomePageLayout}> <View onLayout={onWelcomePageLayout}>
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text> <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> </View>
); );
} }
return ( return <View style={firstRunStyle.container}>{content}</View>;
<View style={firstRunStyle.container}>
{content}
</View>
);
} }
} }

View file

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

View file

@ -30,7 +30,10 @@ const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()), fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)), notify: data => dispatch(doToast(data)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS)), 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 React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
ActivityIndicator,
NativeModules,
ScrollView,
Text,
View
} from 'react-native';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants';
import Link from 'component/link'; import Link from 'component/link';
@ -25,7 +19,7 @@ class RewardsPage extends React.PureComponent {
isRewardApproved: false, isRewardApproved: false,
verifyRequestStarted: false, verifyRequestStarted: false,
revealVerification: true, revealVerification: true,
firstRewardClaimed: false firstRewardClaimed: false,
}; };
scrollView = null; scrollView = null;
@ -51,11 +45,11 @@ class RewardsPage extends React.PureComponent {
fetchRewards(); fetchRewards();
this.setState({ this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email), isEmailVerified: user && user.primary_email && user.has_verified_email,
isIdentityVerified: (user && user.is_identity_verified), isIdentityVerified: user && user.is_identity_verified,
isRewardApproved: (user && user.is_reward_approved) isRewardApproved: user && user.is_reward_approved,
}); });
} };
componentDidMount() { componentDidMount() {
this.onComponentFocused(); this.onComponentFocused();
@ -83,9 +77,9 @@ class RewardsPage extends React.PureComponent {
if (user) { if (user) {
// update other checks (if new user data has been retrieved) // update other checks (if new user data has been retrieved)
this.setState({ this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email), isEmailVerified: user && user.primary_email && user.has_verified_email,
isIdentityVerified: (user && user.is_identity_verified), isIdentityVerified: user && user.is_identity_verified,
isRewardApproved: (user && user.is_reward_approved) isRewardApproved: user && user.is_reward_approved,
}); });
} }
@ -111,7 +105,13 @@ class RewardsPage extends React.PureComponent {
<View style={[rewardStyle.card, rewardStyle.verification]}> <View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Manual Reward Verification</Text> <Text style={rewardStyle.title}>Manual Reward Verification</Text>
<Text style={rewardStyle.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> </Text>
</View> </View>
); );
@ -122,7 +122,7 @@ class RewardsPage extends React.PureComponent {
renderUnclaimedRewards() { renderUnclaimedRewards() {
const { claimed, fetching, rewards, user } = this.props; const { claimed, fetching, rewards, user } = this.props;
const unclaimedRewards = (rewards && rewards.length) ? rewards : []; const unclaimedRewards = rewards && rewards.length ? rewards : [];
if (fetching) { if (fetching) {
return ( return (
@ -142,11 +142,15 @@ class RewardsPage extends React.PureComponent {
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved; const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
return ( return (
<View> <View>
{unclaimedRewards.map(reward => <RewardCard key={reward.reward_type} {unclaimedRewards.map(reward => (
showVerification={this.showVerification} <RewardCard
canClaim={!isNotEligible} key={reward.reward_type}
reward={reward} showVerification={this.showVerification}
reward_type={reward.reward_type} />)} canClaim={!isNotEligible}
reward={reward}
reward_type={reward.reward_type}
/>
))}
<CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} /> <CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} />
</View> </View>
); );
@ -158,7 +162,9 @@ class RewardsPage extends React.PureComponent {
const reversed = claimed.reverse(); const reversed = claimed.reverse();
return ( return (
<View> <View>
{reversed.map(reward => <RewardCard key={reward.transaction_id} reward={reward} />)} {reversed.map(reward => (
<RewardCard key={reward.transaction_id} reward={reward} />
))}
</View> </View>
); );
} }
@ -170,7 +176,7 @@ class RewardsPage extends React.PureComponent {
this.scrollView.scrollTo({ x: 0, y: 0, animated: true }); this.scrollView.scrollTo({ x: 0, y: 0, animated: true });
} }
}); });
} };
render() { render() {
const { user, navigation } = this.props; const { user, navigation } = this.props;
@ -178,18 +184,19 @@ class RewardsPage extends React.PureComponent {
return ( return (
<View style={rewardStyle.container}> <View style={rewardStyle.container}>
<UriBar navigation={navigation} /> <UriBar navigation={navigation} />
{(!this.state.isEmailVerified || !this.state.isRewardApproved) && {(!this.state.isEmailVerified || !this.state.isRewardApproved) && <RewardEnrolment navigation={navigation} />}
<RewardEnrolment navigation={navigation} />}
{(this.state.isEmailVerified && this.state.isRewardApproved) && {this.state.isEmailVerified && this.state.isRewardApproved && (
<ScrollView <ScrollView
ref={ref => this.scrollView = ref} ref={ref => (this.scrollView = ref)}
keyboardShouldPersistTaps={'handled'} keyboardShouldPersistTaps={'handled'}
style={rewardStyle.scrollContainer} style={rewardStyle.scrollContainer}
contentContainerStyle={rewardStyle.scrollContentContainer}> contentContainerStyle={rewardStyle.scrollContentContainer}
>
{this.renderUnclaimedRewards()} {this.renderUnclaimedRewards()}
{this.renderClaimedRewards()} {this.renderClaimedRewards()}
</ScrollView>} </ScrollView>
)}
</View> </View>
); );
} }

View file

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

View file

@ -1,13 +1,6 @@
import React from 'react'; import React from 'react';
import { Lbry, parseURI, normalizeURI, isURIValid } from 'lbry-redux'; import { Lbry, parseURI, normalizeURI, isURIValid } from 'lbry-redux';
import { import { ActivityIndicator, Button, Text, TextInput, View, ScrollView } from 'react-native';
ActivityIndicator,
Button,
Text,
TextInput,
View,
ScrollView
} from 'react-native';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; import Constants from 'constants';
@ -21,10 +14,10 @@ class SearchPage extends React.PureComponent {
state = { state = {
currentQuery: null, currentQuery: null,
currentUri: null, currentUri: null,
} };
static navigationOptions = { static navigationOptions = {
title: 'Search Results' title: 'Search Results',
}; };
didFocusListener; didFocusListener;
@ -49,11 +42,11 @@ class SearchPage extends React.PureComponent {
if (searchQuery && searchQuery.trim().length > 0) { if (searchQuery && searchQuery.trim().length > 0) {
this.setState({ this.setState({
currentQuery: searchQuery, currentQuery: searchQuery,
currentUri: (isURIValid(searchQuery)) ? normalizeURI(searchQuery) : null currentUri: isURIValid(searchQuery) ? normalizeURI(searchQuery) : null,
}); });
search(searchQuery); search(searchQuery);
} }
} };
componentDidMount() { componentDidMount() {
this.onComponentFocused(); this.onComponentFocused();
@ -70,7 +63,7 @@ class SearchPage extends React.PureComponent {
if (query && query.trim().length > 0 && query !== this.state.currentQuery) { if (query && query.trim().length > 0 && query !== this.state.currentQuery) {
this.setState({ this.setState({
currentQuery: query, currentQuery: query,
currentUri: (isURIValid(query)) ? normalizeURI(query) : null currentUri: isURIValid(query) ? normalizeURI(query) : null,
}); });
search(query); search(query);
} }
@ -84,52 +77,61 @@ class SearchPage extends React.PureComponent {
return null; return null;
} }
handleSearchSubmitted = (keywords) => { handleSearchSubmitted = keywords => {
const { search } = this.props; const { search } = this.props;
this.setState({ currentUri: (isURIValid(keywords)) ? normalizeURI(keywords) : null }); this.setState({ currentUri: isURIValid(keywords) ? normalizeURI(keywords) : null });
search(keywords); search(keywords);
} };
render() { render() {
const { isSearching, navigation, query, uris, urisByQuery } = this.props; const { isSearching, navigation, query, uris, urisByQuery } = this.props;
return ( return (
<View style={searchStyle.container}> <View style={searchStyle.container}>
<UriBar value={query} <UriBar value={query} navigation={navigation} onSearchSubmitted={this.handleSearchSubmitted} />
navigation={navigation} {isSearching && (
onSearchSubmitted={this.handleSearchSubmitted} />
{isSearching &&
<View style={searchStyle.busyContainer}> <View style={searchStyle.busyContainer}>
<ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} /> <ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} />
</View>} </View>
)}
{!isSearching && (
{!isSearching && <ScrollView
<ScrollView style={searchStyle.scrollContainer}
style={searchStyle.scrollContainer} contentContainerStyle={searchStyle.scrollPadding}
contentContainerStyle={searchStyle.scrollPadding} keyboardShouldPersistTaps={'handled'}
keyboardShouldPersistTaps={'handled'}> >
{this.state.currentUri && {this.state.currentUri && (
<FileListItem <FileListItem
key={this.state.currentUri} key={this.state.currentUri}
uri={this.state.currentUri} uri={this.state.currentUri}
featuredResult={true} featuredResult={true}
style={searchStyle.featuredResultItem} style={searchStyle.featuredResultItem}
navigation={navigation} navigation={navigation}
onPress={() => navigateToUri(navigation, this.state.currentUri)} onPress={() => navigateToUri(navigation, this.state.currentUri)}
/>} />
{(uris && uris.length) ? ( )}
uris.map(uri => <FileListItem key={uri} {uris && uris.length
uri={uri} ? uris.map(uri => (
style={searchStyle.resultItem} <FileListItem
navigation={navigation} key={uri}
onPress={() => navigateToUri(navigation, uri)}/>) uri={uri}
) : null } style={searchStyle.resultItem}
{(!uris || uris.length === 0) && navigation={navigation}
<View style={searchStyle.noResults}> onPress={() => navigateToUri(navigation, uri)}
<Text style={searchStyle.noResultsText}>There are no results to display for <Text style={searchStyle.boldText}>{query}</Text>. Please try a different search term.</Text> />
</View>} ))
</ScrollView>} : null}
{(!uris || uris.length === 0) && (
<View style={searchStyle.noResults}>
<Text style={searchStyle.noResultsText}>
There are no results to display for <Text style={searchStyle.boldText}>{query}</Text>. Please try a
different search term.
</Text>
</View>
)}
</ScrollView>
)}
<FloatingWalletBalance navigation={navigation} /> <FloatingWalletBalance navigation={navigation} />
</View> </View>
); );

View file

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

View file

@ -8,8 +8,8 @@ import settingsStyle from 'styles/settings';
class SettingsPage extends React.PureComponent { class SettingsPage extends React.PureComponent {
static navigationOptions = { static navigationOptions = {
title: 'Settings' title: 'Settings',
} };
didFocusListener; didFocusListener;
@ -28,7 +28,7 @@ class SettingsPage extends React.PureComponent {
const { pushDrawerStack, setPlayerVisible } = this.props; const { pushDrawerStack, setPlayerVisible } = this.props;
pushDrawerStack(); pushDrawerStack();
setPlayerVisible(); setPlayerVisible();
} };
componentDidMount() { componentDidMount() {
this.onComponentFocused(); this.onComponentFocused();
@ -50,24 +50,29 @@ class SettingsPage extends React.PureComponent {
navigation, navigation,
popDrawerStack, popDrawerStack,
showNsfw, showNsfw,
setClientSetting setClientSetting,
} = this.props; } = this.props;
// default to true if the setting is null or undefined // default to true if the setting is null or undefined
const actualKeepDaemonRunning = (keepDaemonRunning === null || keepDaemonRunning === undefined) ? true : keepDaemonRunning; const actualKeepDaemonRunning =
keepDaemonRunning === null || keepDaemonRunning === undefined ? true : keepDaemonRunning;
return ( return (
<View style={settingsStyle.container}> <View style={settingsStyle.container}>
<PageHeader title={"Settings"} <PageHeader title={'Settings'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
<ScrollView style={settingsStyle.scrollContainer}> <ScrollView style={settingsStyle.scrollContainer}>
<View style={settingsStyle.row}> <View style={settingsStyle.row}>
<View style={settingsStyle.switchText}> <View style={settingsStyle.switchText}>
<Text style={settingsStyle.label}>Enable background media playback</Text> <Text style={settingsStyle.label}>Enable background media playback</Text>
<Text style={settingsStyle.description}>Enable this option to play audio or video in the background when the app is suspended.</Text> <Text style={settingsStyle.description}>
Enable this option to play audio or video in the background when the app is suspended.
</Text>
</View> </View>
<View style={settingsStyle.switchContainer}> <View style={settingsStyle.switchContainer}>
<Switch value={backgroundPlayEnabled} onValueChange={(value) => setClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED, value)} /> <Switch
value={backgroundPlayEnabled}
onValueChange={value => setClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED, value)}
/>
</View> </View>
</View> </View>
@ -76,22 +81,28 @@ class SettingsPage extends React.PureComponent {
<Text style={settingsStyle.label}>Show NSFW content</Text> <Text style={settingsStyle.label}>Show NSFW content</Text>
</View> </View>
<View style={settingsStyle.switchContainer}> <View style={settingsStyle.switchContainer}>
<Switch value={showNsfw} onValueChange={(value) => setClientSetting(SETTINGS.SHOW_NSFW, value)} /> <Switch value={showNsfw} onValueChange={value => setClientSetting(SETTINGS.SHOW_NSFW, value)} />
</View> </View>
</View> </View>
<View style={settingsStyle.row}> <View style={settingsStyle.row}>
<View style={settingsStyle.switchText}> <View style={settingsStyle.switchText}>
<Text style={settingsStyle.label}>Keep the daemon background service running after closing the app</Text> <Text style={settingsStyle.label}>Keep the daemon background service running after closing the app</Text>
<Text style={settingsStyle.description}>Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to date.</Text> <Text style={settingsStyle.description}>
Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to
date.
</Text>
</View> </View>
<View style={settingsStyle.switchContainer}> <View style={settingsStyle.switchContainer}>
<Switch value={actualKeepDaemonRunning} onValueChange={(value) => { <Switch
setClientSetting(SETTINGS.KEEP_DAEMON_RUNNING, value); value={actualKeepDaemonRunning}
if (NativeModules.DaemonServiceControl) { onValueChange={value => {
NativeModules.DaemonServiceControl.setKeepDaemonRunning(value); setClientSetting(SETTINGS.KEEP_DAEMON_RUNNING, value);
} if (NativeModules.DaemonServiceControl) {
}} /> NativeModules.DaemonServiceControl.setKeepDaemonRunning(value);
}
}}
/>
</View> </View>
</View> </View>
</ScrollView> </ScrollView>

View file

@ -11,14 +11,14 @@ import {
doUserEmailVerify, doUserEmailVerify,
doUserEmailVerifyFailure, doUserEmailVerifyFailure,
selectUser, selectUser,
selectEmailToVerify selectEmailToVerify,
} from 'lbryinc'; } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import SplashScreen from './view'; import SplashScreen from './view';
const select = state => ({ const select = state => ({
user: selectUser(state), user: selectUser(state),
emailToVerify: selectEmailToVerify(state) emailToVerify: selectEmailToVerify(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -27,14 +27,17 @@ const perform = dispatch => ({
blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()), blacklistedOutpointsSubscribe: () => dispatch(doBlackListedOutpointsSubscribe()),
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()), checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchSubscriptions: (callback) => dispatch(doFetchMySubscriptions(callback)), fetchSubscriptions: callback => dispatch(doFetchMySubscriptions(callback)),
getSync: password => dispatch(doGetSync(password)), getSync: password => dispatch(doGetSync(password)),
notify: data => dispatch(doToast(data)), notify: data => dispatch(doToast(data)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)), setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
updateBlockHeight: () => dispatch(doUpdateBlockHeight()), updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)), verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)),
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)) verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
}); });
export default connect(select, perform)(SplashScreen); export default connect(
select,
perform
)(SplashScreen);

View file

@ -1,13 +1,6 @@
import React from 'react'; import React from 'react';
import { Lbry } from 'lbry-redux'; import { Lbry } from 'lbry-redux';
import { import { ActivityIndicator, Linking, NativeModules, Platform, Text, View } from 'react-native';
ActivityIndicator,
Linking,
NativeModules,
Platform,
Text,
View
} from 'react-native';
import { NavigationActions, StackActions } from 'react-navigation'; import { NavigationActions, StackActions } from 'react-navigation';
import { decode as atob } from 'base-64'; import { decode as atob } from 'base-64';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
@ -23,7 +16,7 @@ const BLOCK_HEIGHT_INTERVAL = 1000 * 60 * 2.5; // every 2.5 minutes
class SplashScreen extends React.PureComponent { class SplashScreen extends React.PureComponent {
static navigationOptions = { static navigationOptions = {
title: 'Splash' title: 'Splash',
}; };
componentWillMount() { componentWillMount() {
@ -37,7 +30,7 @@ class SplashScreen extends React.PureComponent {
isDownloadingHeaders: false, isDownloadingHeaders: false,
headersDownloadProgress: 0, headersDownloadProgress: 0,
shouldAuthenticate: false, shouldAuthenticate: false,
subscriptionsFetched: false subscriptionsFetched: false,
}); });
if (NativeModules.DaemonServiceControl) { if (NativeModules.DaemonServiceControl) {
@ -55,9 +48,7 @@ class SplashScreen extends React.PureComponent {
const { navigation } = this.props; const { navigation } = this.props;
const resetAction = StackActions.reset({ const resetAction = StackActions.reset({
index: 0, index: 0,
actions: [ actions: [NavigationActions.navigate({ routeName: 'Main' })],
NavigationActions.navigate({ routeName: 'Main'})
]
}); });
navigation.dispatch(resetAction); navigation.dispatch(resetAction);
@ -88,16 +79,10 @@ class SplashScreen extends React.PureComponent {
navigateToUri(navigation, launchUrl); navigateToUri(navigation, launchUrl);
} }
} }
} };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { const { emailToVerify, getSync, setEmailToVerify, verifyUserEmail, verifyUserEmailFailure } = this.props;
emailToVerify,
getSync,
setEmailToVerify,
verifyUserEmail,
verifyUserEmailFailure
} = this.props;
const { user } = nextProps; const { user } = nextProps;
if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) { if (this.state.daemonReady && this.state.shouldAuthenticate && user && user.id) {
@ -128,7 +113,7 @@ class SplashScreen extends React.PureComponent {
navigation, navigation,
notify, notify,
updateBlockHeight, updateBlockHeight,
user user,
} = this.props; } = this.props;
Lbry.resolve({ urls: 'lbry://one' }).then(() => { Lbry.resolve({ urls: 'lbry://one' }).then(() => {
@ -137,7 +122,9 @@ class SplashScreen extends React.PureComponent {
blacklistedOutpointsSubscribe(); blacklistedOutpointsSubscribe();
checkSubscriptionsInit(); checkSubscriptionsInit();
updateBlockHeight(); updateBlockHeight();
setInterval(() => { updateBlockHeight(); }, BLOCK_HEIGHT_INTERVAL); setInterval(() => {
updateBlockHeight();
}, BLOCK_HEIGHT_INTERVAL);
if (user && user.id && user.has_verified_email) { if (user && user.id && user.has_verified_email) {
// user already authenticated // user already authenticated
@ -154,7 +141,7 @@ class SplashScreen extends React.PureComponent {
}); });
} }
}); });
} };
_updateStatusCallback(status) { _updateStatusCallback(status) {
const { fetchSubscriptions, getSync, setClientSetting } = this.props; const { fetchSubscriptions, getSync, setClientSetting } = this.props;
@ -174,12 +161,13 @@ class SplashScreen extends React.PureComponent {
isRunning: true, isRunning: true,
}); });
// For now, automatically unlock the wallet if a password is set so that downloads work // For now, automatically unlock the wallet if a password is set so that downloads work
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(password => { NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(password => {
if (password && password.trim().length > 0) { if (password && password.trim().length > 0) {
// unlock the wallet and then finish the splash screen // unlock the wallet and then finish the splash screen
Lbry.account_unlock({ password }).then(() => this.finishSplashScreen()).catch(() => this.finishSplashScreen()); Lbry.account_unlock({ password })
.then(() => this.finishSplashScreen())
.catch(() => this.finishSplashScreen());
return; return;
} }
@ -195,7 +183,7 @@ class SplashScreen extends React.PureComponent {
if (blockchainHeaders) { if (blockchainHeaders) {
this.setState({ this.setState({
isDownloadingHeaders: blockchainHeaders.downloading_headers, isDownloadingHeaders: blockchainHeaders.downloading_headers,
headersDownloadProgress: blockchainHeaders.download_progress headersDownloadProgress: blockchainHeaders.download_progress,
}); });
} else { } else {
// set downloading flag to false if blockchain_headers isn't in the status response // set downloading flag to false if blockchain_headers isn't in the status response
@ -220,7 +208,7 @@ class SplashScreen extends React.PureComponent {
} else { } else {
this.setState({ this.setState({
message: 'Network Loading', message: 'Network Loading',
details: 'Initializing LBRY service...' details: 'Initializing LBRY service...',
}); });
} }
@ -235,7 +223,7 @@ class SplashScreen extends React.PureComponent {
} }
this.props.fetchRewardedContent(); this.props.fetchRewardedContent();
Linking.getInitialURL().then((url) => { Linking.getInitialURL().then(url => {
if (url) { if (url) {
this.setState({ launchUrl: url }); this.setState({ launchUrl: url });
} }
@ -251,17 +239,16 @@ class SplashScreen extends React.PureComponent {
} }
}); });
Lbry Lbry.connect()
.connect()
.then(() => { .then(() => {
this.updateStatus(); this.updateStatus();
}) })
.catch((e) => { .catch(e => {
this.setState({ this.setState({
isLagging: true, isLagging: true,
message: 'Connection Failure', message: 'Connection Failure',
details: details:
'We could not establish a connection to the daemon. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.' 'We could not establish a connection to the daemon. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.',
}); });
}); });
} }
@ -272,12 +259,16 @@ class SplashScreen extends React.PureComponent {
return ( return (
<View style={splashStyle.container}> <View style={splashStyle.container}>
<Text style={splashStyle.title}>LBRY</Text> <Text style={splashStyle.title}>LBRY</Text>
{this.state.isDownloadingHeaders && {this.state.isDownloadingHeaders && (
<ProgressBar <ProgressBar
color={Colors.White} color={Colors.White}
style={splashStyle.progress} style={splashStyle.progress}
progress={this.state.headersDownloadProgress} />} progress={this.state.headersDownloadProgress}
{!this.state.isDownloadingHeaders && <ActivityIndicator color={Colors.White} style={splashStyle.loading} size={"small"} />} />
)}
{!this.state.isDownloadingHeaders && (
<ActivityIndicator color={Colors.White} style={splashStyle.loading} size={'small'} />
)}
<Text style={splashStyle.message}>{message}</Text> <Text style={splashStyle.message}>{message}</Text>
<Text style={splashStyle.details}>{details}</Text> <Text style={splashStyle.details}>{details}</Text>
</View> </View>

View file

@ -10,7 +10,7 @@ import {
selectUnreadSubscriptions, selectUnreadSubscriptions,
selectViewMode, selectViewMode,
selectFirstRunCompleted, selectFirstRunCompleted,
selectShowSuggestedSubs selectShowSuggestedSubs,
} from 'lbryinc'; } from 'lbryinc';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
@ -21,9 +21,7 @@ import SubscriptionsPage from './view';
const select = state => ({ const select = state => ({
currentRoute: selectCurrentRoute(state), currentRoute: selectCurrentRoute(state),
loading: loading: selectIsFetchingSubscriptions(state) || Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
selectIsFetchingSubscriptions(state) ||
Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
subscriptionsViewMode: makeSelectClientSetting(Constants.SETTING_SUBSCRIPTIONS_VIEW_MODE)(state), subscriptionsViewMode: makeSelectClientSetting(Constants.SETTING_SUBSCRIPTIONS_VIEW_MODE)(state),
allSubscriptions: selectSubscriptionClaims(state), allSubscriptions: selectSubscriptionClaims(state),
@ -36,10 +34,13 @@ const select = state => ({
const perform = dispatch => ({ const perform = dispatch => ({
doFetchMySubscriptions: () => dispatch(doFetchMySubscriptions()), doFetchMySubscriptions: () => dispatch(doFetchMySubscriptions()),
doFetchRecommendedSubscriptions: () => dispatch(doFetchRecommendedSubscriptions()), doFetchRecommendedSubscriptions: () => dispatch(doFetchRecommendedSubscriptions()),
doSetViewMode: (viewMode) => dispatch(doSetViewMode(viewMode)), doSetViewMode: viewMode => dispatch(doSetViewMode(viewMode)),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SUBSCRIPTIONS)), pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SUBSCRIPTIONS)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)) setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
}); });
export default connect(select, perform)(SubscriptionsPage); export default connect(
select,
perform
)(SubscriptionsPage);

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