Publishing #577
152 changed files with 4771 additions and 2637 deletions
36
app/.eslintrc.json
Normal file
36
app/.eslintrc.json
Normal 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
6
app/.lintstagedrc.json
Normal 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
5
app/.prettierrc.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"singleQuote": true
|
||||
}
|
1580
app/package-lock.json
generated
1580
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,9 @@
|
|||
"version": "0.0.1",
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start"
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"format": "prettier 'src/**/*.{js,json}' --write",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"base-64": "^0.1.0",
|
||||
|
@ -43,6 +45,17 @@
|
|||
"babel-preset-react-native": "5.0.2",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
"babel-plugin-module-resolver": "^3.1.1",
|
||||
"flow-babel-webpack-plugin": "^1.1.1"
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-config-standard-jsx": "^6.0.2",
|
||||
"eslint-plugin-flowtype": "^2.46.1",
|
||||
"eslint-plugin-import": "^2.17.2",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"flow-babel-webpack-plugin": "^1.1.1",
|
||||
"lint-staged": "^7.0.4",
|
||||
"prettier": "^1.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,25 +15,14 @@ import SubscriptionsPage from 'page/subscriptions';
|
|||
import TransactionHistoryPage from 'page/transactionHistory';
|
||||
import VerificationScreen from 'page/verification';
|
||||
import WalletPage from 'page/wallet';
|
||||
import {
|
||||
createDrawerNavigator,
|
||||
createStackNavigator,
|
||||
NavigationActions
|
||||
} from 'react-navigation';
|
||||
import { createDrawerNavigator, createStackNavigator, NavigationActions } from 'react-navigation';
|
||||
import {
|
||||
createReduxContainer,
|
||||
createReactNavigationReduxMiddleware,
|
||||
createNavigationReducer
|
||||
createNavigationReducer,
|
||||
} from 'react-navigation-redux-helpers';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
AppState,
|
||||
BackHandler,
|
||||
Linking,
|
||||
NativeModules,
|
||||
TextInput,
|
||||
ToastAndroid
|
||||
} from 'react-native';
|
||||
import { AppState, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native';
|
||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
|
||||
import {
|
||||
|
@ -44,7 +33,7 @@ import {
|
|||
selectEmailToVerify,
|
||||
selectEmailVerifyIsPending,
|
||||
selectEmailVerifyErrorMessage,
|
||||
selectUser
|
||||
selectUser,
|
||||
} from 'lbryinc';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { decode as atob } from 'base-64';
|
||||
|
@ -58,37 +47,43 @@ import discoverStyle from 'styles/discover';
|
|||
import searchStyle from 'styles/search';
|
||||
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
|
||||
|
||||
const menuNavigationButton = (navigation) => <NavigationButton
|
||||
name="bars"
|
||||
size={24}
|
||||
style={discoverStyle.drawerMenuButton}
|
||||
iconStyle={discoverStyle.drawerHamburger}
|
||||
onPress={() => navigation.openDrawer() } />
|
||||
const menuNavigationButton = navigation => (
|
||||
<NavigationButton
|
||||
name="bars"
|
||||
size={24}
|
||||
style={discoverStyle.drawerMenuButton}
|
||||
iconStyle={discoverStyle.drawerHamburger}
|
||||
onPress={() => navigation.openDrawer()}
|
||||
/>
|
||||
);
|
||||
|
||||
const discoverStack = createStackNavigator({
|
||||
Discover: {
|
||||
screen: DiscoverPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Explore',
|
||||
header: null
|
||||
}),
|
||||
const discoverStack = createStackNavigator(
|
||||
{
|
||||
Discover: {
|
||||
screen: DiscoverPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Explore',
|
||||
header: null,
|
||||
}),
|
||||
},
|
||||
File: {
|
||||
screen: FilePage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
header: null,
|
||||
}),
|
||||
},
|
||||
Search: {
|
||||
screen: SearchPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
header: null,
|
||||
}),
|
||||
},
|
||||
},
|
||||
File: {
|
||||
screen: FilePage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
header: null
|
||||
})
|
||||
},
|
||||
Search: {
|
||||
screen: SearchPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
header: null
|
||||
})
|
||||
{
|
||||
headerMode: 'screen',
|
||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||
}
|
||||
}, {
|
||||
headerMode: 'screen',
|
||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||
});
|
||||
);
|
||||
|
||||
discoverStack.navigationOptions = ({ navigation }) => {
|
||||
let drawerLockMode = 'unlocked';
|
||||
|
@ -97,105 +92,137 @@ discoverStack.navigationOptions = ({ navigation }) => {
|
|||
}*/
|
||||
|
||||
return {
|
||||
drawerLockMode
|
||||
drawerLockMode,
|
||||
};
|
||||
};
|
||||
|
||||
const walletStack = createStackNavigator({
|
||||
Wallet: {
|
||||
screen: WalletPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Wallet',
|
||||
header: null
|
||||
})
|
||||
const walletStack = createStackNavigator(
|
||||
{
|
||||
Wallet: {
|
||||
screen: WalletPage,
|
||||
navigationOptions: ({ navigation }) => ({
|
||||
title: 'Wallet',
|
||||
header: null,
|
||||
}),
|
||||
},
|
||||
TransactionHistory: {
|
||||
screen: TransactionHistoryPage,
|
||||
navigationOptions: {
|
||||
title: 'Transaction History',
|
||||
header: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
TransactionHistory: {
|
||||
screen: TransactionHistoryPage,
|
||||
navigationOptions: {
|
||||
title: 'Transaction History',
|
||||
header: null
|
||||
}
|
||||
{
|
||||
headerMode: 'screen',
|
||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||
}
|
||||
}, {
|
||||
headerMode: 'screen',
|
||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||
});
|
||||
);
|
||||
|
||||
const drawer = createDrawerNavigator({
|
||||
DiscoverStack: { screen: discoverStack, navigationOptions: {
|
||||
title: 'Explore', drawerIcon: ({ tintColor }) => <Icon name="home" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
TrendingStack: { screen: TrendingPage, navigationOptions: {
|
||||
title: 'Trending', drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
MySubscriptionsStack: { screen: SubscriptionsPage, navigationOptions: {
|
||||
title: 'Subscriptions', drawerIcon: ({ tintColor }) => <Icon name="heart" solid={true} size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
WalletStack: { screen: walletStack, navigationOptions: {
|
||||
title: 'Wallet', drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
Rewards: { screen: RewardsPage, navigationOptions: {
|
||||
drawerIcon: ({ tintColor }) => <Icon name="award" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
Publish: { screen: PublishPage, navigationOptions: {
|
||||
drawerIcon: ({ tintColor }) => <Icon name="upload" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
MyLBRYStack: { screen: DownloadsPage, navigationOptions: {
|
||||
title: 'Library', drawerIcon: ({ tintColor }) => <Icon name="download" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
Settings: { screen: SettingsPage, navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />
|
||||
}},
|
||||
About: { screen: AboutPage, navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />
|
||||
}}
|
||||
}, {
|
||||
drawerWidth: 300,
|
||||
headerMode: 'none',
|
||||
contentComponent: DrawerContent,
|
||||
contentOptions: {
|
||||
activeTintColor: Colors.LbryGreen,
|
||||
labelStyle: discoverStyle.menuText
|
||||
const drawer = createDrawerNavigator(
|
||||
{
|
||||
DiscoverStack: {
|
||||
screen: discoverStack,
|
||||
navigationOptions: {
|
||||
title: 'Explore',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="home" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
TrendingStack: {
|
||||
screen: TrendingPage,
|
||||
navigationOptions: {
|
||||
title: 'Trending',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
MySubscriptionsStack: {
|
||||
screen: SubscriptionsPage,
|
||||
navigationOptions: {
|
||||
title: 'Subscriptions',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="heart" solid={true} size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
WalletStack: {
|
||||
screen: walletStack,
|
||||
navigationOptions: {
|
||||
title: 'Wallet',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
Rewards: {
|
||||
screen: RewardsPage,
|
||||
navigationOptions: {
|
||||
drawerIcon: ({ tintColor }) => <Icon name="award" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
MyLBRYStack: {
|
||||
screen: DownloadsPage,
|
||||
navigationOptions: {
|
||||
title: 'Library',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="download" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
Settings: {
|
||||
screen: SettingsPage,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
About: {
|
||||
screen: AboutPage,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
drawerWidth: 300,
|
||||
headerMode: 'none',
|
||||
contentComponent: DrawerContent,
|
||||
contentOptions: {
|
||||
activeTintColor: Colors.LbryGreen,
|
||||
labelStyle: discoverStyle.menuText,
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const mainStackNavigator = new createStackNavigator({
|
||||
FirstRun: {
|
||||
screen: FirstRunScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed'
|
||||
}
|
||||
const mainStackNavigator = new createStackNavigator(
|
||||
{
|
||||
FirstRun: {
|
||||
screen: FirstRunScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
},
|
||||
},
|
||||
Splash: {
|
||||
screen: SplashScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
},
|
||||
},
|
||||
Main: {
|
||||
screen: drawer,
|
||||
},
|
||||
Verification: {
|
||||
screen: VerificationScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed',
|
||||
},
|
||||
},
|
||||
},
|
||||
Splash: {
|
||||
screen: SplashScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed'
|
||||
}
|
||||
},
|
||||
Main: {
|
||||
screen: drawer
|
||||
},
|
||||
Verification: {
|
||||
screen: VerificationScreen,
|
||||
navigationOptions: {
|
||||
drawerLockMode: 'locked-closed'
|
||||
}
|
||||
{
|
||||
headerMode: 'none',
|
||||
}
|
||||
}, {
|
||||
headerMode: 'none'
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
export const AppNavigator = mainStackNavigator;
|
||||
export const navigatorReducer = createNavigationReducer(AppNavigator);
|
||||
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
|
||||
state => state.nav,
|
||||
);
|
||||
export const reactNavigationMiddleware = createReactNavigationReduxMiddleware(state => state.nav);
|
||||
|
||||
const App = createReduxContainer(mainStackNavigator);
|
||||
const appMapStateToProps = (state) => ({
|
||||
const appMapStateToProps = state => ({
|
||||
state: state.nav,
|
||||
});
|
||||
const ReduxAppNavigator = connect(appMapStateToProps)(App);
|
||||
|
@ -208,29 +235,34 @@ class AppWithNavigationState extends React.Component {
|
|||
this.emailVerifyCheckInterval = null;
|
||||
this.state = {
|
||||
emailVerifyDone: false,
|
||||
verifyPending: false
|
||||
verifyPending: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppState.addEventListener('change', this._handleAppStateChange);
|
||||
BackHandler.addEventListener('hardwareBackPress', function() {
|
||||
const { dispatch, nav, drawerStack } = this.props;
|
||||
// There should be a better way to check this
|
||||
if (nav.routes.length > 0) {
|
||||
if (nav.routes[0].routeName === 'Main') {
|
||||
const mainRoute = nav.routes[0];
|
||||
if (mainRoute.index > 0 ||
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
function() {
|
||||
const { dispatch, nav, drawerStack } = this.props;
|
||||
// There should be a better way to check this
|
||||
if (nav.routes.length > 0) {
|
||||
if (nav.routes[0].routeName === 'Main') {
|
||||
const mainRoute = nav.routes[0];
|
||||
if (
|
||||
mainRoute.index > 0 ||
|
||||
mainRoute.routes[0].index > 0 /* Discover stack index */ ||
|
||||
mainRoute.routes[4].index > 0 /* Wallet stack index */ ||
|
||||
mainRoute.index >= 5 /* Settings and About screens */) {
|
||||
dispatchNavigateBack(dispatch, nav, drawerStack);
|
||||
return true;
|
||||
mainRoute.index >= 5 /* Settings and About screens */
|
||||
) {
|
||||
dispatchNavigateBack(dispatch, nav, drawerStack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}.bind(this));
|
||||
return false;
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -241,12 +273,12 @@ class AppWithNavigationState extends React.Component {
|
|||
checkEmailVerification = () => {
|
||||
const { dispatch } = this.props;
|
||||
AsyncStorage.getItem(Constants.KEY_EMAIL_VERIFY_PENDING).then(pending => {
|
||||
this.setState({ verifyPending: ('true' === pending) });
|
||||
this.setState({ verifyPending: 'true' === pending });
|
||||
if ('true' === pending) {
|
||||
dispatch(doUserCheckEmailVerified());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this._handleAppStateChange);
|
||||
|
@ -274,13 +306,7 @@ class AppWithNavigationState extends React.Component {
|
|||
|
||||
componentWillUpdate(nextProps) {
|
||||
const { dispatch } = this.props;
|
||||
const {
|
||||
toast,
|
||||
emailToVerify,
|
||||
emailVerifyPending,
|
||||
emailVerifyErrorMessage,
|
||||
user
|
||||
} = nextProps;
|
||||
const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps;
|
||||
|
||||
if (toast) {
|
||||
const { message } = toast;
|
||||
|
@ -297,15 +323,13 @@ class AppWithNavigationState extends React.Component {
|
|||
dispatch(doDismissToast());
|
||||
}
|
||||
|
||||
if (user &&
|
||||
!emailVerifyPending &&
|
||||
!this.state.emailVerifyDone &&
|
||||
(emailToVerify || emailVerifyErrorMessage)) {
|
||||
if (user && !emailVerifyPending && !this.state.emailVerifyDone && (emailToVerify || emailVerifyErrorMessage)) {
|
||||
AsyncStorage.getItem(Constants.KEY_SHOULD_VERIFY_EMAIL).then(shouldVerify => {
|
||||
if ('true' === shouldVerify) {
|
||||
this.setState({ emailVerifyDone: true });
|
||||
const message = emailVerifyErrorMessage ?
|
||||
String(emailVerifyErrorMessage) : 'Your email address was successfully verified.';
|
||||
const message = emailVerifyErrorMessage
|
||||
? String(emailVerifyErrorMessage)
|
||||
: 'Your email address was successfully verified.';
|
||||
if (!emailVerifyErrorMessage) {
|
||||
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
|
||||
}
|
||||
|
@ -317,7 +341,7 @@ class AppWithNavigationState extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_handleAppStateChange = (nextAppState) => {
|
||||
_handleAppStateChange = nextAppState => {
|
||||
const { backgroundPlayEnabled, dispatch } = this.props;
|
||||
// Check if the app was suspended
|
||||
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
|
||||
|
@ -341,9 +365,9 @@ class AppWithNavigationState extends React.Component {
|
|||
NativeModules.BackgroundMedia.hidePlaybackNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_handleUrl = (evt) => {
|
||||
_handleUrl = evt => {
|
||||
const { dispatch, nav } = this.props;
|
||||
if (evt.url) {
|
||||
if (evt.url.startsWith('lbry://?verify=')) {
|
||||
|
@ -365,15 +389,17 @@ class AppWithNavigationState extends React.Component {
|
|||
dispatch(doToast({ message }));
|
||||
}
|
||||
} else {
|
||||
dispatch(doToast({
|
||||
message: 'Invalid Verification URI',
|
||||
}));
|
||||
dispatch(
|
||||
doToast({
|
||||
message: 'Invalid Verification URI',
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dispatchNavigateToUri(dispatch, nav, evt.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ReduxAppNavigator />;
|
||||
|
@ -390,7 +416,7 @@ const mapStateToProps = state => ({
|
|||
emailVerifyPending: selectEmailVerifyIsPending(state),
|
||||
emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||
user: selectUser(state)
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AppWithNavigationState);
|
||||
|
|
|
@ -2,6 +2,9 @@ import { connect } from 'react-redux';
|
|||
import { doToast } from 'lbry-redux';
|
||||
import Address from './view';
|
||||
|
||||
export default connect(null, {
|
||||
doToast,
|
||||
})(Address);
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
doToast,
|
||||
}
|
||||
)(Address);
|
||||
|
|
|
@ -15,13 +15,19 @@ export default class Address extends React.PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<View style={[walletStyle.row, style]}>
|
||||
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>{address || ''}</Text>
|
||||
<Button icon={'clipboard'} style={walletStyle.button} onPress={() => {
|
||||
Clipboard.setString(address);
|
||||
doToast({
|
||||
message: 'Address copied',
|
||||
});
|
||||
}} />
|
||||
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>
|
||||
{address || ''}
|
||||
</Text>
|
||||
<Button
|
||||
icon={'clipboard'}
|
||||
style={walletStyle.button}
|
||||
onPress={() => {
|
||||
Clipboard.setString(address);
|
||||
doToast({
|
||||
message: 'Address copied',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import Button from './view';
|
||||
|
||||
export default connect(null, null)(Button);
|
||||
export default connect(
|
||||
null,
|
||||
null
|
||||
)(Button);
|
||||
|
|
|
@ -6,17 +6,7 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
|
|||
|
||||
export default class Button extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
disabled,
|
||||
style,
|
||||
text,
|
||||
icon,
|
||||
iconColor,
|
||||
solid,
|
||||
theme,
|
||||
onPress,
|
||||
onLayout
|
||||
} = this.props;
|
||||
const { disabled, style, text, icon, iconColor, solid, theme, onPress, onLayout } = this.props;
|
||||
|
||||
let styles = [buttonStyle.button, buttonStyle.row];
|
||||
if (style) {
|
||||
|
@ -43,16 +33,25 @@ export default class Button extends React.PureComponent {
|
|||
textStyles.push(buttonStyle.textLight);
|
||||
}
|
||||
|
||||
let renderIcon = (<Icon name={icon} size={18} color={iconColor ? iconColor : ('light' === theme ? Colors.DarkGrey : Colors.White)} />);
|
||||
let renderIcon = (
|
||||
<Icon name={icon} size={18} color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White} />
|
||||
);
|
||||
if (solid) {
|
||||
renderIcon = (<Icon name={icon} size={18} color={iconColor ? iconColor : ('light' === theme ? Colors.DarkGrey : Colors.White)} solid />);
|
||||
renderIcon = (
|
||||
<Icon
|
||||
name={icon}
|
||||
size={18}
|
||||
color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White}
|
||||
solid
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity disabled={disabled} style={styles} onPress={onPress} onLayout={onLayout}>
|
||||
{icon && renderIcon}
|
||||
{text && (text.trim().length > 0) && <Text style={textStyles}>{text}</Text>}
|
||||
{text && text.trim().length > 0 && <Text style={textStyles}>{text}</Text>}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import CategoryList from './view';
|
||||
|
||||
export default connect(null, null)(CategoryList);
|
||||
export default connect(
|
||||
null,
|
||||
null
|
||||
)(CategoryList);
|
||||
|
|
|
@ -16,7 +16,7 @@ class CategoryList extends React.PureComponent {
|
|||
initialNumToRender={3}
|
||||
maxToRenderPerBatch={3}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={ ({item}) => (
|
||||
renderItem={({ item }) => (
|
||||
<FileItem
|
||||
style={discoverStyle.fileItem}
|
||||
mediaStyle={discoverStyle.fileItemMedia}
|
||||
|
@ -24,9 +24,9 @@ class CategoryList extends React.PureComponent {
|
|||
uri={normalizeURI(item)}
|
||||
navigation={navigation}
|
||||
showDetails={true}
|
||||
compactView={false} />
|
||||
)
|
||||
}
|
||||
compactView={false}
|
||||
/>
|
||||
)}
|
||||
horizontal={true}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
data={categoryMap[category]}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
doClaimRewardClearError,
|
||||
makeSelectClaimRewardError,
|
||||
makeSelectIsRewardClaimPending,
|
||||
rewards as REWARD_TYPES
|
||||
rewards as REWARD_TYPES,
|
||||
} from 'lbryinc';
|
||||
import CustomRewardCard from './view';
|
||||
|
||||
|
@ -20,7 +20,10 @@ const perform = dispatch => ({
|
|||
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
|
||||
clearError: reward => dispatch(doClaimRewardClearError(reward)),
|
||||
notify: data => dispatch(doToast(data)),
|
||||
submitRewardCode: code => dispatch(doClaimRewardType(REWARD_TYPES.TYPE_REWARD_CODE, { params: { code } }))
|
||||
submitRewardCode: code => dispatch(doClaimRewardType(REWARD_TYPES.TYPE_REWARD_CODE, { params: { code } })),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(CustomRewardCard);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(CustomRewardCard);
|
||||
|
|
|
@ -10,7 +10,7 @@ import rewardStyle from '../../styles/reward';
|
|||
class CustomRewardCard extends React.PureComponent<Props> {
|
||||
state = {
|
||||
claimStarted: false,
|
||||
rewardCode: ''
|
||||
rewardCode: '',
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -49,31 +49,39 @@ class CustomRewardCard extends React.PureComponent<Props> {
|
|||
this.setState({ claimStarted: true }, () => {
|
||||
submitRewardCode(rewardCode);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { canClaim, rewardIsPending } = this.props;
|
||||
|
||||
return (
|
||||
<View style={[rewardStyle.rewardCard, rewardStyle.row]} >
|
||||
<View style={[rewardStyle.rewardCard, rewardStyle.row]}>
|
||||
<View style={rewardStyle.leftCol}>
|
||||
{rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
|
||||
</View>
|
||||
<View style={rewardStyle.midCol}>
|
||||
<Text style={rewardStyle.rewardTitle}>Custom Code</Text>
|
||||
<Text style={rewardStyle.rewardDescription}>Are you a supermodel or rockstar that received a custom reward code? Claim it here.</Text>
|
||||
<Text style={rewardStyle.rewardDescription}>
|
||||
Are you a supermodel or rockstar that received a custom reward code? Claim it here.
|
||||
</Text>
|
||||
|
||||
<View>
|
||||
<TextInput style={rewardStyle.customCodeInput}
|
||||
placeholder={"0123abc"}
|
||||
onChangeText={text => this.setState({ rewardCode: text })}
|
||||
value={this.state.rewardCode} />
|
||||
<Button style={rewardStyle.redeemButton}
|
||||
text={"Redeem"}
|
||||
disabled={(!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending)}
|
||||
onPress={() => {
|
||||
if (!rewardIsPending) { this.onClaimPress(); }
|
||||
}} />
|
||||
<TextInput
|
||||
style={rewardStyle.customCodeInput}
|
||||
placeholder={'0123abc'}
|
||||
onChangeText={text => this.setState({ rewardCode: text })}
|
||||
value={this.state.rewardCode}
|
||||
/>
|
||||
<Button
|
||||
style={rewardStyle.redeemButton}
|
||||
text={'Redeem'}
|
||||
disabled={!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending}
|
||||
onPress={() => {
|
||||
if (!rewardIsPending) {
|
||||
this.onClaimPress();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={rewardStyle.rightCol}>
|
||||
|
@ -83,6 +91,6 @@ class CustomRewardCard extends React.PureComponent<Props> {
|
|||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CustomRewardCard;
|
||||
|
|
|
@ -29,7 +29,11 @@ class DateTime extends React.PureComponent<Props> {
|
|||
const locale = 'en-US'; // default to en-US until we get a working i18n module for RN
|
||||
|
||||
if (timeAgo) {
|
||||
return date ? <View style={style}><Text style={textStyle}>{moment(date).from(moment())}</Text></View> : null;
|
||||
return date ? (
|
||||
<View style={style}>
|
||||
<Text style={textStyle}>{moment(date).from(moment())}</Text>
|
||||
</View>
|
||||
) : null;
|
||||
}
|
||||
|
||||
// TODO: formatOptions not working as expected in RN
|
||||
|
@ -38,13 +42,9 @@ class DateTime extends React.PureComponent<Props> {
|
|||
return (
|
||||
<View style={style}>
|
||||
<Text style={textStyle}>
|
||||
{date &&
|
||||
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) &&
|
||||
moment(date).format('MMMM D, YYYY')}
|
||||
{date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && moment(date).format('MMMM D, YYYY')}
|
||||
{show === DateTime.SHOW_BOTH && ' '}
|
||||
{date &&
|
||||
(show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) &&
|
||||
date.toLocaleTimeString()}
|
||||
{date && (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()}
|
||||
{!date && '...'}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -14,7 +14,7 @@ class DrawerContent extends React.PureComponent {
|
|||
<SafeAreaView style={discoverStyle.drawerContentContainer} forceInset={{ top: 'always', horizontal: 'never' }}>
|
||||
<DrawerItems
|
||||
{...props}
|
||||
onItemPress={(route) => {
|
||||
onItemPress={route => {
|
||||
const { routeName } = route.route;
|
||||
if (Constants.FULL_ROUTE_NAME_DISCOVER === routeName) {
|
||||
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_DISCOVER });
|
||||
|
@ -28,7 +28,7 @@ class DrawerContent extends React.PureComponent {
|
|||
|
||||
onItemPress(route);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
);
|
||||
|
|
|
@ -22,4 +22,7 @@ const perform = dispatch => ({
|
|||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileDownloadButton);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileDownloadButton);
|
||||
|
|
|
@ -49,13 +49,14 @@ class FileDownloadButton extends React.PureComponent {
|
|||
} = this.props;
|
||||
|
||||
if ((fileInfo && !fileInfo.stopped) || loading || downloading) {
|
||||
const progress =
|
||||
fileInfo && fileInfo.written_bytes ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
|
||||
const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0,
|
||||
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...';
|
||||
|
||||
return (
|
||||
<View style={[style, fileDownloadButtonStyle.container]}>
|
||||
<View style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}></View>
|
||||
<View
|
||||
style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}
|
||||
></View>
|
||||
<Text style={fileDownloadButtonStyle.text}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
|
@ -68,29 +69,35 @@ class FileDownloadButton extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
return (
|
||||
<Button icon={isPlayable ? 'play' : null}
|
||||
text={(isPlayable ? 'Play' : (isViewable ? 'View' : 'Download'))}
|
||||
onLayout={onButtonLayout}
|
||||
style={[style, fileDownloadButtonStyle.container]} onPress={() => {
|
||||
if (NativeModules.Firebase) {
|
||||
NativeModules.Firebase.track('purchase_uri', { uri: uri });
|
||||
}
|
||||
purchaseUri(uri, costInfo, !isPlayable);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.checkDownloads();
|
||||
}
|
||||
if (isPlayable && onPlay) {
|
||||
this.props.onPlay();
|
||||
}
|
||||
if (isViewable && onView) {
|
||||
this.props.onView();
|
||||
}
|
||||
}} />
|
||||
<Button
|
||||
icon={isPlayable ? 'play' : null}
|
||||
text={isPlayable ? 'Play' : isViewable ? 'View' : 'Download'}
|
||||
onLayout={onButtonLayout}
|
||||
style={[style, fileDownloadButtonStyle.container]}
|
||||
onPress={() => {
|
||||
if (NativeModules.Firebase) {
|
||||
NativeModules.Firebase.track('purchase_uri', { uri: uri });
|
||||
}
|
||||
purchaseUri(uri, costInfo, !isPlayable);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.checkDownloads();
|
||||
}
|
||||
if (isPlayable && onPlay) {
|
||||
this.props.onPlay();
|
||||
}
|
||||
if (isViewable && onView) {
|
||||
this.props.onView();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (fileInfo && fileInfo.download_path) {
|
||||
return (
|
||||
<TouchableOpacity onLayout={onButtonLayout}
|
||||
style={[style, fileDownloadButtonStyle.container]} onPress={openFile}>
|
||||
<TouchableOpacity
|
||||
onLayout={onButtonLayout}
|
||||
style={[style, fileDownloadButtonStyle.container]}
|
||||
onPress={openFile}
|
||||
>
|
||||
<Text style={fileDownloadButtonStyle.text}>{isViewable ? 'View' : 'Open'}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
makeSelectThumbnailForUri,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectIsUriResolving,
|
||||
makeSelectClaimIsNsfw
|
||||
makeSelectClaimIsNsfw,
|
||||
} from 'lbry-redux';
|
||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
|
@ -29,4 +29,7 @@ const perform = dispatch => ({
|
|||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileItem);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileItem);
|
||||
|
|
|
@ -40,7 +40,7 @@ class FileItem extends React.PureComponent {
|
|||
NativeModules.Firebase.track('explore_click', { uri: normalizedUri });
|
||||
}
|
||||
navigateToUri(navigation, normalizedUri);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -56,46 +56,73 @@ class FileItem extends React.PureComponent {
|
|||
navigation,
|
||||
showDetails,
|
||||
compactView,
|
||||
titleBeforeThumbnail
|
||||
titleBeforeThumbnail,
|
||||
} = this.props;
|
||||
|
||||
const uri = normalizeURI(this.props.uri);
|
||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||
const channelName = claim ? claim.channel_name : null;
|
||||
const channelClaimId = claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
||||
const channelClaimId =
|
||||
claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
||||
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
|
||||
const height = claim ? claim.height : null;
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<TouchableOpacity style={discoverStyle.container} onPress={this.navigateToFileUri}>
|
||||
{!compactView && titleBeforeThumbnail && <Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>}
|
||||
<FileItemMedia title={title}
|
||||
thumbnail={thumbnail}
|
||||
blurRadius={obscureNsfw ? 15 : 0}
|
||||
resizeMode="cover"
|
||||
isResolvingUri={isResolvingUri}
|
||||
style={mediaStyle} />
|
||||
{!compactView && titleBeforeThumbnail && (
|
||||
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
<FileItemMedia
|
||||
title={title}
|
||||
thumbnail={thumbnail}
|
||||
blurRadius={obscureNsfw ? 15 : 0}
|
||||
resizeMode="cover"
|
||||
isResolvingUri={isResolvingUri}
|
||||
style={mediaStyle}
|
||||
/>
|
||||
|
||||
{(!compactView && fileInfo && fileInfo.completed && fileInfo.download_path) &&
|
||||
<Icon style={discoverStyle.downloadedIcon} solid={true} color={Colors.NextLbryGreen} name={"folder"} size={16} />}
|
||||
{(!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path)) &&
|
||||
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />}
|
||||
{!compactView && <View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
||||
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>{title}</Text>
|
||||
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={14} />}
|
||||
</View>}
|
||||
{(!compactView && showDetails) &&
|
||||
<View style={discoverStyle.detailsRow}>
|
||||
{channelName &&
|
||||
<Link style={discoverStyle.channelName} text={channelName} onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}} />}
|
||||
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
|
||||
</View>}
|
||||
{!compactView && fileInfo && fileInfo.completed && fileInfo.download_path && (
|
||||
<Icon
|
||||
style={discoverStyle.downloadedIcon}
|
||||
solid={true}
|
||||
color={Colors.NextLbryGreen}
|
||||
name={'folder'}
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
{!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
|
||||
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
|
||||
)}
|
||||
{!compactView && (
|
||||
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
||||
<Text numberOfLines={1} style={[discoverStyle.fileItemName, discoverStyle.rewardTitle]}>
|
||||
{title}
|
||||
</Text>
|
||||
{isRewardContent && <Icon style={discoverStyle.rewardIcon} name="award" size={14} />}
|
||||
</View>
|
||||
)}
|
||||
{!compactView && showDetails && (
|
||||
<View style={discoverStyle.detailsRow}>
|
||||
{channelName && (
|
||||
<Link
|
||||
style={discoverStyle.channelName}
|
||||
text={channelName}
|
||||
onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
|
||||
{obscureNsfw && (
|
||||
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,4 +4,7 @@ import FileItemMedia from './view';
|
|||
const select = state => ({});
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(select, perform)(FileItemMedia);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileItemMedia);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { ActivityIndicator, Image, Text, View } from 'react-native';
|
||||
import Colors from 'styles/colors';
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import fileItemMediaStyle from 'styles/fileItemMedia';
|
||||
|
||||
class FileItemMedia extends React.PureComponent {
|
||||
|
@ -20,33 +20,31 @@ class FileItemMedia extends React.PureComponent {
|
|||
];
|
||||
|
||||
state: {
|
||||
imageLoadFailed: false
|
||||
imageLoadFailed: false,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
autoThumbStyle:
|
||||
FileItemMedia.AUTO_THUMB_STYLES[
|
||||
Math.floor(Math.random() * FileItemMedia.AUTO_THUMB_STYLES.length)
|
||||
],
|
||||
FileItemMedia.AUTO_THUMB_STYLES[Math.floor(Math.random() * FileItemMedia.AUTO_THUMB_STYLES.length)],
|
||||
});
|
||||
}
|
||||
|
||||
getFastImageResizeMode(resizeMode) {
|
||||
switch (resizeMode) {
|
||||
case "contain":
|
||||
case 'contain':
|
||||
return FastImage.resizeMode.contain;
|
||||
case "stretch":
|
||||
case 'stretch':
|
||||
return FastImage.resizeMode.stretch;
|
||||
case "center":
|
||||
case 'center':
|
||||
return FastImage.resizeMode.center;
|
||||
default:
|
||||
return FastImage.resizeMode.cover;
|
||||
}
|
||||
}
|
||||
|
||||
isThumbnailValid = (thumbnail) => {
|
||||
if (!thumbnail || ((typeof thumbnail) !== 'string')) {
|
||||
isThumbnailValid = thumbnail => {
|
||||
if (!thumbnail || typeof thumbnail !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -55,7 +53,7 @@ class FileItemMedia extends React.PureComponent {
|
|||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let style = this.props.style;
|
||||
|
@ -70,16 +68,17 @@ class FileItemMedia extends React.PureComponent {
|
|||
// No blur radius support in FastImage yet
|
||||
return (
|
||||
<Image
|
||||
source={{uri: thumbnail}}
|
||||
source={{ uri: thumbnail }}
|
||||
blurRadius={blurRadius}
|
||||
resizeMode={resizeMode ? resizeMode : "cover"}
|
||||
resizeMode={resizeMode ? resizeMode : 'cover'}
|
||||
style={style}
|
||||
/>);
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
source={{uri: thumbnail}}
|
||||
source={{ uri: thumbnail }}
|
||||
onError={() => this.setState({ imageLoadFailed: true })}
|
||||
resizeMode={this.getFastImageResizeMode(resizeMode)}
|
||||
style={style}
|
||||
|
@ -91,15 +90,19 @@ class FileItemMedia extends React.PureComponent {
|
|||
<View style={[style ? style : fileItemMediaStyle.autothumb, atStyle]}>
|
||||
{isResolvingUri && (
|
||||
<View style={fileItemMediaStyle.resolving}>
|
||||
<ActivityIndicator color={Colors.White} size={"large"} />
|
||||
<ActivityIndicator color={Colors.White} size={'large'} />
|
||||
<Text style={fileItemMediaStyle.text}>Resolving...</Text>
|
||||
</View>
|
||||
)}
|
||||
{!isResolvingUri && <Text style={fileItemMediaStyle.autothumbText}>{title &&
|
||||
title
|
||||
.replace(/\s+/g, '')
|
||||
.substring(0, Math.min(title.replace(' ', '').length, 5))
|
||||
.toUpperCase()}</Text>}
|
||||
{!isResolvingUri && (
|
||||
<Text style={fileItemMediaStyle.autothumbText}>
|
||||
{title &&
|
||||
title
|
||||
.replace(/\s+/g, '')
|
||||
.substring(0, Math.min(title.replace(' ', '').length, 5))
|
||||
.toUpperCase()}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,4 +8,7 @@ const select = state => ({
|
|||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(select, perform)(FileList);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileList);
|
||||
|
|
|
@ -145,15 +145,7 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
sortFunctions: {};
|
||||
|
||||
render() {
|
||||
const {
|
||||
contentContainerStyle,
|
||||
fileInfos,
|
||||
hideFilter,
|
||||
checkPending,
|
||||
navigation,
|
||||
onEndReached,
|
||||
style
|
||||
} = this.props;
|
||||
const { contentContainerStyle, fileInfos, hideFilter, checkPending, navigation, onEndReached, style } = this.props;
|
||||
const { sortBy } = this.state;
|
||||
const items = [];
|
||||
|
||||
|
@ -182,13 +174,16 @@ class FileList extends React.PureComponent<Props, State> {
|
|||
data={items}
|
||||
onEndReached={onEndReached}
|
||||
keyExtractor={(item, index) => item}
|
||||
renderItem={({item}) => (
|
||||
<FileItem style={fileListStyle.fileItem}
|
||||
uri={item}
|
||||
navigation={navigation}
|
||||
showDetails={true}
|
||||
compactView={false} />
|
||||
)} />
|
||||
renderItem={({ item }) => (
|
||||
<FileItem
|
||||
style={fileListStyle.fileItem}
|
||||
uri={item}
|
||||
navigation={navigation}
|
||||
showDetails={true}
|
||||
compactView={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ const select = (state, props) => ({
|
|||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resolveUri: uri => dispatch(doResolveUri(uri))
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileListItem);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileListItem);
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import React from 'react';
|
||||
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { navigateToUri, formatBytes } from 'utils/helper';
|
||||
import Colors from 'styles/colors';
|
||||
import DateTime from 'component/dateTime';
|
||||
|
@ -18,7 +12,7 @@ import ProgressBar from 'component/progressBar';
|
|||
import fileListStyle from 'styles/fileList';
|
||||
|
||||
class FileListItem extends React.PureComponent {
|
||||
getStorageForFileInfo = (fileInfo) => {
|
||||
getStorageForFileInfo = fileInfo => {
|
||||
if (!fileInfo.completed) {
|
||||
const written = formatBytes(fileInfo.written_bytes);
|
||||
const total = formatBytes(fileInfo.total_bytes);
|
||||
|
@ -26,19 +20,19 @@ class FileListItem extends React.PureComponent {
|
|||
}
|
||||
|
||||
return formatBytes(fileInfo.written_bytes);
|
||||
}
|
||||
};
|
||||
|
||||
formatTitle = (title) => {
|
||||
formatTitle = title => {
|
||||
if (!title) {
|
||||
return title;
|
||||
}
|
||||
|
||||
return (title.length > 80) ? title.substring(0, 77).trim() + '...' : title;
|
||||
}
|
||||
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
|
||||
};
|
||||
|
||||
getDownloadProgress = (fileInfo) => {
|
||||
getDownloadProgress = fileInfo => {
|
||||
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { claim, resolveUri, uri } = this.props;
|
||||
|
@ -59,7 +53,7 @@ class FileListItem extends React.PureComponent {
|
|||
onPress,
|
||||
navigation,
|
||||
thumbnail,
|
||||
title
|
||||
title,
|
||||
} = this.props;
|
||||
|
||||
const uri = normalizeURI(this.props.uri);
|
||||
|
@ -82,50 +76,80 @@ class FileListItem extends React.PureComponent {
|
|||
return (
|
||||
<View style={style}>
|
||||
<TouchableOpacity style={style} onPress={onPress}>
|
||||
<FileItemMedia style={fileListStyle.thumbnail}
|
||||
blurRadius={obscureNsfw ? 15 : 0}
|
||||
resizeMode="cover"
|
||||
title={(title || name)}
|
||||
thumbnail={thumbnail} />
|
||||
{(fileInfo && fileInfo.completed && fileInfo.download_path) &&
|
||||
<Icon style={fileListStyle.downloadedIcon} solid={true} color={Colors.NextLbryGreen} name={"folder"} size={16} />}
|
||||
<FileItemMedia
|
||||
style={fileListStyle.thumbnail}
|
||||
blurRadius={obscureNsfw ? 15 : 0}
|
||||
resizeMode="cover"
|
||||
title={title || name}
|
||||
thumbnail={thumbnail}
|
||||
/>
|
||||
{fileInfo && fileInfo.completed && fileInfo.download_path && (
|
||||
<Icon
|
||||
style={fileListStyle.downloadedIcon}
|
||||
solid={true}
|
||||
color={Colors.NextLbryGreen}
|
||||
name={'folder'}
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
<View style={fileListStyle.detailsContainer}>
|
||||
{featuredResult && <Text style={fileListStyle.featuredUri} numberOfLines={1}>{uri}</Text>}
|
||||
{featuredResult && (
|
||||
<Text style={fileListStyle.featuredUri} numberOfLines={1}>
|
||||
{uri}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{!title && !name && !channel && isResolving && (
|
||||
<View>
|
||||
{(!title && !name) && <Text style={fileListStyle.uri}>{uri}</Text>}
|
||||
{(!title && !name) && <View style={fileListStyle.row}>
|
||||
<ActivityIndicator size={"small"} color={featuredResult ? Colors.White : Colors.LbryGreen} />
|
||||
</View>}
|
||||
</View>)}
|
||||
<View>
|
||||
{!title && !name && <Text style={fileListStyle.uri}>{uri}</Text>}
|
||||
{!title && !name && (
|
||||
<View style={fileListStyle.row}>
|
||||
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.LbryGreen} />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(title || name) && <Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>{this.formatTitle(title) || this.formatTitle(name)}</Text>}
|
||||
{channel &&
|
||||
<Link style={fileListStyle.publisher} text={channel} onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}} />}
|
||||
{(title || name) && (
|
||||
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
|
||||
{this.formatTitle(title) || this.formatTitle(name)}
|
||||
</Text>
|
||||
)}
|
||||
{channel && (
|
||||
<Link
|
||||
style={fileListStyle.publisher}
|
||||
text={channel}
|
||||
onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={fileListStyle.info}>
|
||||
{(fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0) &&
|
||||
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>}
|
||||
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
|
||||
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>
|
||||
)}
|
||||
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
|
||||
</View>
|
||||
|
||||
{(fileInfo && fileInfo.download_path) &&
|
||||
{fileInfo && fileInfo.download_path && (
|
||||
<View style={fileListStyle.downloadInfo}>
|
||||
{!fileInfo.completed &&
|
||||
{!fileInfo.completed && (
|
||||
<ProgressBar
|
||||
borderRadius={3}
|
||||
color={Colors.NextLbryGreen}
|
||||
height={3}
|
||||
style={fileListStyle.progress}
|
||||
progress={this.getDownloadProgress(fileInfo)} />}
|
||||
progress={this.getDownloadProgress(fileInfo)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{obscureNsfw && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
|
||||
{obscureNsfw && (
|
||||
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,4 +13,7 @@ const perform = dispatch => ({
|
|||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FilePrice);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FilePrice);
|
||||
|
|
|
@ -46,9 +46,7 @@ class CreditAmount extends React.PureComponent {
|
|||
} else {
|
||||
if (this.props.label) {
|
||||
const label =
|
||||
typeof this.props.label === 'string'
|
||||
? this.props.label
|
||||
: parseFloat(amount) == 1 ? 'credit' : 'credits';
|
||||
typeof this.props.label === 'string' ? this.props.label : parseFloat(amount) == 1 ? 'credit' : 'credits';
|
||||
|
||||
amountText = `${formattedAmount} ${label}`;
|
||||
} else {
|
||||
|
@ -67,9 +65,7 @@ class CreditAmount extends React.PureComponent {
|
|||
*
|
||||
</span>
|
||||
) : null}*/
|
||||
return (
|
||||
<Text style={style}>{amountText}</Text>
|
||||
);
|
||||
return <Text style={style}>{amountText}</Text>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +96,7 @@ class FilePrice extends React.PureComponent {
|
|||
<View style={style}>
|
||||
<Text style={textStyle}>???</Text>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -111,7 +107,10 @@ class FilePrice extends React.PureComponent {
|
|||
amount={parseFloat(costInfo.cost)}
|
||||
isEstimate={isEstimate}
|
||||
showFree
|
||||
showFullPrice={showFullPrice}>???</CreditAmount>
|
||||
showFullPrice={showFullPrice}
|
||||
>
|
||||
???
|
||||
</CreditAmount>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,4 +11,7 @@ const select = state => ({
|
|||
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(FloatingWalletBalance);
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(FloatingWalletBalance);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { formatCredits } from 'lbry-redux'
|
||||
import { formatCredits } from 'lbry-redux';
|
||||
import Address from 'component/address';
|
||||
import Button from 'component/button';
|
||||
import Colors from 'styles/colors';
|
||||
|
@ -18,17 +18,23 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
|
|||
|
||||
return (
|
||||
<View style={[floatingButtonStyle.view, floatingButtonStyle.bottomRight]}>
|
||||
{(!rewardsNotInterested && unclaimedRewardAmount > 0) &&
|
||||
<TouchableOpacity style={floatingButtonStyle.pendingContainer}
|
||||
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })} >
|
||||
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
|
||||
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
|
||||
</TouchableOpacity>}
|
||||
<TouchableOpacity style={floatingButtonStyle.container}
|
||||
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}>
|
||||
{!rewardsNotInterested && unclaimedRewardAmount > 0 && (
|
||||
<TouchableOpacity
|
||||
style={floatingButtonStyle.pendingContainer}
|
||||
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })}
|
||||
>
|
||||
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
|
||||
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={floatingButtonStyle.container}
|
||||
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}
|
||||
>
|
||||
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
|
||||
{(!isNaN(balance) || balance === 0) && (
|
||||
<Text style={floatingButtonStyle.text}>{(formatCredits(parseFloat(balance), 2) + ' LBC')}</Text>)}
|
||||
<Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 2) + ' LBC'}</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
class Gallery extends React.PureComponent {
|
||||
render() {
|
||||
|
||||
}
|
||||
render() {}
|
||||
}
|
||||
|
||||
export default Gallery;
|
||||
|
|
|
@ -3,7 +3,10 @@ import { doToast } from 'lbry-redux';
|
|||
import Link from './view';
|
||||
|
||||
const perform = dispatch => ({
|
||||
notify: (data) => dispatch(doToast(data))
|
||||
notify: data => dispatch(doToast(data)),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(Link);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(Link);
|
||||
|
|
|
@ -2,13 +2,12 @@ import React from 'react';
|
|||
import { Linking, Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
export default class Link extends React.PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
super(props);
|
||||
this.state = {
|
||||
tappedStyle: false,
|
||||
}
|
||||
this.addTappedStyle = this.addTappedStyle.bind(this)
|
||||
};
|
||||
this.addTappedStyle = this.addTappedStyle.bind(this);
|
||||
}
|
||||
|
||||
handlePress = () => {
|
||||
|
@ -19,28 +18,27 @@ export default class Link extends React.PureComponent {
|
|||
} else {
|
||||
if (this.props.effectOnTap) this.addTappedStyle();
|
||||
Linking.openURL(href)
|
||||
.then(() => setTimeout(() => { this.setState({ tappedStyle: false }); }, 2000))
|
||||
.catch(err => {
|
||||
notify({ message: error, isError: true })
|
||||
this.setState({tappedStyle: false})
|
||||
}
|
||||
);
|
||||
.then(() =>
|
||||
setTimeout(() => {
|
||||
this.setState({ tappedStyle: false });
|
||||
}, 2000)
|
||||
)
|
||||
.catch(err => {
|
||||
notify({ message: error, isError: true });
|
||||
this.setState({ tappedStyle: false });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addTappedStyle() {
|
||||
this.setState({ tappedStyle: true });
|
||||
setTimeout(() => { this.setState({ tappedStyle: false }); }, 2000);
|
||||
setTimeout(() => {
|
||||
this.setState({ tappedStyle: false });
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
ellipsizeMode,
|
||||
numberOfLines,
|
||||
onPress,
|
||||
style,
|
||||
text
|
||||
} = this.props;
|
||||
const { ellipsizeMode, numberOfLines, onPress, style, text } = this.props;
|
||||
|
||||
let styles = [];
|
||||
if (style) {
|
||||
|
@ -60,9 +58,10 @@ export default class Link extends React.PureComponent {
|
|||
style={styles}
|
||||
numberOfLines={numberOfLines}
|
||||
ellipsizeMode={ellipsizeMode}
|
||||
onPress={onPress ? onPress : this.handlePress}>
|
||||
onPress={onPress ? onPress : this.handlePress}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,4 +15,7 @@ const perform = dispatch => ({
|
|||
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(MediaPlayer);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(MediaPlayer);
|
||||
|
|
|
@ -9,16 +9,16 @@ import {
|
|||
Text,
|
||||
View,
|
||||
ScrollView,
|
||||
TouchableOpacity
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Colors from 'styles/colors';
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Video from 'react-native-video';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||
import FileItemMedia from 'component/fileItemMedia';
|
||||
import mediaPlayerStyle from 'styles/mediaPlayer';
|
||||
|
||||
const positionSaveInterval = 10
|
||||
const positionSaveInterval = 10;
|
||||
|
||||
class MediaPlayer extends React.PureComponent {
|
||||
static ControlsTimeout = 3000;
|
||||
|
@ -52,13 +52,15 @@ class MediaPlayer extends React.PureComponent {
|
|||
seekerOffset: 0,
|
||||
seekerPosition: 0,
|
||||
firstPlay: true,
|
||||
seekTimeout: -1
|
||||
seekTimeout: -1,
|
||||
};
|
||||
}
|
||||
|
||||
formatTime(time) {
|
||||
let str = '';
|
||||
let minutes = 0, hours = 0, seconds = parseInt(time, 10);
|
||||
let minutes = 0,
|
||||
hours = 0,
|
||||
seconds = parseInt(time, 10);
|
||||
if (seconds > 60) {
|
||||
minutes = parseInt(seconds / 60, 10);
|
||||
seconds = seconds % 60;
|
||||
|
@ -84,9 +86,9 @@ class MediaPlayer extends React.PureComponent {
|
|||
return value;
|
||||
}
|
||||
|
||||
onLoad = (data) => {
|
||||
onLoad = data => {
|
||||
this.setState({
|
||||
duration: data.duration
|
||||
duration: data.duration,
|
||||
});
|
||||
|
||||
const { position } = this.props;
|
||||
|
@ -98,9 +100,9 @@ class MediaPlayer extends React.PureComponent {
|
|||
if (this.props.onMediaLoaded) {
|
||||
this.props.onMediaLoaded();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onProgress = (data) => {
|
||||
onProgress = data => {
|
||||
const { savePosition, claim } = this.props;
|
||||
|
||||
this.setState({ buffering: false, currentTime: data.currentTime });
|
||||
|
@ -121,13 +123,13 @@ class MediaPlayer extends React.PureComponent {
|
|||
|
||||
this.hidePlayerControls();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
clearControlsTimeout = () => {
|
||||
if (this.state.controlsTimeout > -1) {
|
||||
clearTimeout(this.state.controlsTimeout)
|
||||
clearTimeout(this.state.controlsTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
showPlayerControls = () => {
|
||||
this.clearControlsTimeout();
|
||||
|
@ -135,12 +137,12 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.setState({ areControlsVisible: true });
|
||||
}
|
||||
this.hidePlayerControls();
|
||||
}
|
||||
};
|
||||
|
||||
manualHidePlayerControls = () => {
|
||||
this.clearControlsTimeout();
|
||||
this.setState({ areControlsVisible: false });
|
||||
}
|
||||
};
|
||||
|
||||
hidePlayerControls() {
|
||||
const player = this;
|
||||
|
@ -161,19 +163,19 @@ class MediaPlayer extends React.PureComponent {
|
|||
} else {
|
||||
this.showPlayerControls();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
togglePlay = () => {
|
||||
this.showPlayerControls();
|
||||
this.setState({ paused: !this.state.paused }, this.handlePausedState);
|
||||
}
|
||||
};
|
||||
|
||||
handlePausedState = () => {
|
||||
if (!this.state.paused) {
|
||||
// onProgress will automatically clear this, so it's fine
|
||||
this.setState({ buffering: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggleFullscreenMode = () => {
|
||||
this.showPlayerControls();
|
||||
|
@ -183,7 +185,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
onFullscreenToggled(this.state.fullscreenMode);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onEnd = () => {
|
||||
this.setState({ paused: true });
|
||||
|
@ -191,7 +193,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.props.onPlaybackFinished();
|
||||
}
|
||||
this.video.seek(0);
|
||||
}
|
||||
};
|
||||
|
||||
setSeekerPosition(position = 0) {
|
||||
position = this.checkSeekerPosition(position);
|
||||
|
@ -244,10 +246,14 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.onEnd();
|
||||
} else {
|
||||
this.seekTo(time);
|
||||
this.setState({ seekTimeout: setTimeout(() => { this.setState({ seeking: false }); }, 100) });
|
||||
this.setState({
|
||||
seekTimeout: setTimeout(() => {
|
||||
this.setState({ seeking: false });
|
||||
}, 100),
|
||||
});
|
||||
}
|
||||
this.hidePlayerControls();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -268,7 +274,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.initSeeker();
|
||||
|
@ -283,7 +289,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { assignPlayer, backgroundPlayEnabled } = this.props;
|
||||
const { assignPlayer, backgroundPlayEnabled } = this.props;
|
||||
if (assignPlayer) {
|
||||
assignPlayer(this);
|
||||
}
|
||||
|
@ -319,21 +325,21 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.setState({ paused: false, autoPaused: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onBuffer = () => {
|
||||
if (!this.state.paused) {
|
||||
this.setState({ buffering: true }, () => this.manualHidePlayerControls());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
play = () => {
|
||||
this.setState({ paused: false }, this.updateBackgroundMediaNotification);
|
||||
}
|
||||
};
|
||||
|
||||
pause = () => {
|
||||
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
|
||||
}
|
||||
};
|
||||
|
||||
updateBackgroundMediaNotification = () => {
|
||||
this.handlePausedState();
|
||||
|
@ -344,7 +350,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
NativeModules.BackgroundMedia.showPlaybackNotification(title, channel, uri, this.state.paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderPlayerControls() {
|
||||
const { onBackButtonPressed } = this.props;
|
||||
|
@ -353,11 +359,10 @@ class MediaPlayer extends React.PureComponent {
|
|||
return (
|
||||
<View style={mediaPlayerStyle.playerControlsContainer}>
|
||||
<TouchableOpacity style={mediaPlayerStyle.backButton} onPress={onBackButtonPressed}>
|
||||
<Icon name={"arrow-left"} size={18} style={mediaPlayerStyle.backButtonIcon} />
|
||||
</TouchableOpacity>
|
||||
<Icon name={'arrow-left'} size={18} style={mediaPlayerStyle.backButtonIcon} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={mediaPlayerStyle.playPauseButton}
|
||||
onPress={this.togglePlay}>
|
||||
<TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
|
||||
{this.state.paused && <Icon name="play" size={40} color="#ffffff" />}
|
||||
{!this.state.paused && <Icon name="pause" size={40} color="#ffffff" />}
|
||||
</TouchableOpacity>
|
||||
|
@ -376,7 +381,7 @@ class MediaPlayer extends React.PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
onSeekerTouchAreaPressed = (evt) => {
|
||||
onSeekerTouchAreaPressed = evt => {
|
||||
if (evt && evt.nativeEvent) {
|
||||
const newSeekerPosition = evt.nativeEvent.locationX;
|
||||
if (!isNaN(newSeekerPosition)) {
|
||||
|
@ -385,13 +390,13 @@ class MediaPlayer extends React.PureComponent {
|
|||
this.seekTo(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onTrackingLayout = (evt) => {
|
||||
onTrackingLayout = evt => {
|
||||
this.trackingOffset = evt.nativeEvent.layout.x;
|
||||
this.seekerWidth = evt.nativeEvent.layout.width;
|
||||
this.setSeekerPosition(this.calculateSeekerPosition());
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onLayout, source, style, thumbnail } = this.props;
|
||||
|
@ -406,64 +411,90 @@ class MediaPlayer extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const trackingStyle = [mediaPlayerStyle.trackingControls, this.state.fullscreenMode ?
|
||||
mediaPlayerStyle.fullscreenTrackingControls : mediaPlayerStyle.containedTrackingControls];
|
||||
const trackingStyle = [
|
||||
mediaPlayerStyle.trackingControls,
|
||||
this.state.fullscreenMode
|
||||
? mediaPlayerStyle.fullscreenTrackingControls
|
||||
: mediaPlayerStyle.containedTrackingControls,
|
||||
];
|
||||
|
||||
return (
|
||||
<View style={styles} onLayout={onLayout}>
|
||||
<Video source={{ uri: source }}
|
||||
bufferConfig={{ minBufferMs: 15000, maxBufferMs: 60000, bufferForPlaybackMs: 5000, bufferForPlaybackAfterRebufferMs: 5000 }}
|
||||
ref={(ref: Video) => { this.video = ref; }}
|
||||
resizeMode={this.state.resizeMode}
|
||||
playInBackground={this.state.backgroundPlayEnabled}
|
||||
style={mediaPlayerStyle.player}
|
||||
rate={this.state.rate}
|
||||
volume={this.state.volume}
|
||||
paused={this.state.paused}
|
||||
onLoad={this.onLoad}
|
||||
onBuffer={this.onBuffer}
|
||||
onProgress={this.onProgress}
|
||||
onEnd={this.onEnd}
|
||||
onError={this.onError}
|
||||
minLoadRetryCount={999}
|
||||
/>
|
||||
<Video
|
||||
source={{ uri: source }}
|
||||
bufferConfig={{
|
||||
minBufferMs: 15000,
|
||||
maxBufferMs: 60000,
|
||||
bufferForPlaybackMs: 5000,
|
||||
bufferForPlaybackAfterRebufferMs: 5000,
|
||||
}}
|
||||
ref={(ref: Video) => {
|
||||
this.video = ref;
|
||||
}}
|
||||
resizeMode={this.state.resizeMode}
|
||||
playInBackground={this.state.backgroundPlayEnabled}
|
||||
style={mediaPlayerStyle.player}
|
||||
rate={this.state.rate}
|
||||
volume={this.state.volume}
|
||||
paused={this.state.paused}
|
||||
onLoad={this.onLoad}
|
||||
onBuffer={this.onBuffer}
|
||||
onProgress={this.onProgress}
|
||||
onEnd={this.onEnd}
|
||||
onError={this.onError}
|
||||
minLoadRetryCount={999}
|
||||
/>
|
||||
|
||||
{this.state.firstPlay && thumbnail && thumbnail.trim().length > 0 &&
|
||||
<FastImage
|
||||
source={{uri: thumbnail}}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
style={mediaPlayerStyle.playerThumbnail}
|
||||
/>}
|
||||
{this.state.firstPlay && thumbnail && thumbnail.trim().length > 0 && (
|
||||
<FastImage
|
||||
source={{ uri: thumbnail }}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
style={mediaPlayerStyle.playerThumbnail}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TouchableOpacity style={mediaPlayerStyle.playerControls} onPress={this.togglePlayerControls}>
|
||||
{this.renderPlayerControls()}
|
||||
</TouchableOpacity>
|
||||
|
||||
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) &&
|
||||
<View style={trackingStyle} onLayout={this.onTrackingLayout}>
|
||||
<View style={mediaPlayerStyle.progress}>
|
||||
<View style={[mediaPlayerStyle.innerProgressCompleted, { width: completedWidth }]} />
|
||||
<View style={[mediaPlayerStyle.innerProgressRemaining, { width: remainingWidth }]} />
|
||||
{(!this.state.fullscreenMode || (this.state.fullscreenMode && this.state.areControlsVisible)) && (
|
||||
<View style={trackingStyle} onLayout={this.onTrackingLayout}>
|
||||
<View style={mediaPlayerStyle.progress}>
|
||||
<View style={[mediaPlayerStyle.innerProgressCompleted, { width: completedWidth }]} />
|
||||
<View style={[mediaPlayerStyle.innerProgressRemaining, { width: remainingWidth }]} />
|
||||
</View>
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
|
||||
{this.state.buffering &&
|
||||
<View style={mediaPlayerStyle.loadingContainer}>
|
||||
<ActivityIndicator color={Colors.LbryGreen} size="large" />
|
||||
</View>}
|
||||
|
||||
{this.state.areControlsVisible &&
|
||||
<View style={{ left: this.getTrackingOffset(), width: this.seekerWidth }}>
|
||||
<View style={[mediaPlayerStyle.seekerHandle,
|
||||
(this.state.fullscreenMode ? mediaPlayerStyle.seekerHandleFs : mediaPlayerStyle.seekerHandleContained),
|
||||
{ left: this.state.seekerPosition }]} { ...this.seekResponder.panHandlers }>
|
||||
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />
|
||||
{this.state.buffering && (
|
||||
<View style={mediaPlayerStyle.loadingContainer}>
|
||||
<ActivityIndicator color={Colors.LbryGreen} size="large" />
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[mediaPlayerStyle.seekerTouchArea,
|
||||
(this.state.fullscreenMode ? mediaPlayerStyle.seekerTouchAreaFs : mediaPlayerStyle.seekerTouchAreaContained)]}
|
||||
onPress={this.onSeekerTouchAreaPressed} />
|
||||
</View>}
|
||||
)}
|
||||
|
||||
{this.state.areControlsVisible && (
|
||||
<View style={{ left: this.getTrackingOffset(), width: this.seekerWidth }}>
|
||||
<View
|
||||
style={[
|
||||
mediaPlayerStyle.seekerHandle,
|
||||
this.state.fullscreenMode ? mediaPlayerStyle.seekerHandleFs : mediaPlayerStyle.seekerHandleContained,
|
||||
{ left: this.state.seekerPosition },
|
||||
]}
|
||||
{...this.seekResponder.panHandlers}
|
||||
>
|
||||
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
mediaPlayerStyle.seekerTouchArea,
|
||||
this.state.fullscreenMode
|
||||
? mediaPlayerStyle.seekerTouchAreaFs
|
||||
: mediaPlayerStyle.seekerTouchAreaContained,
|
||||
]}
|
||||
onPress={this.onSeekerTouchAreaPressed}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
|
||||
|
||||
class NavigationButton extends React.PureComponent {
|
||||
render() {
|
||||
const { iconStyle, name, onPress, size, style } = this.props;
|
||||
|
@ -13,6 +12,6 @@ class NavigationButton extends React.PureComponent {
|
|||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default NavigationButton;
|
||||
|
|
|
@ -3,4 +3,7 @@ import NsfwOverlay from './view';
|
|||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(null, perform)(NsfwOverlay);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(NsfwOverlay);
|
||||
|
|
|
@ -6,9 +6,11 @@ class NsfwOverlay extends React.PureComponent {
|
|||
render() {
|
||||
return (
|
||||
<TouchableOpacity style={discoverStyle.overlay} activeOpacity={0.95} onPress={this.props.onPress}>
|
||||
<Text style={discoverStyle.overlayText}>This content is Not Safe For Work. To view adult content, please change your Settings.</Text>
|
||||
<Text style={discoverStyle.overlayText}>
|
||||
This content is Not Safe For Work. To view adult content, please change your Settings.
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,7 @@ import PageHeader from './view';
|
|||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(null, perform)(PageHeader);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(PageHeader);
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
// Based on https://github.com/react-navigation/react-navigation/blob/master/src/views/Header/Header.js
|
||||
import React from 'react';
|
||||
import {
|
||||
Animated,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { Animated, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||
import NavigationButton from 'component/navigationButton';
|
||||
import pageHeaderStyle from 'styles/pageHeader';
|
||||
|
@ -18,20 +11,14 @@ const AnimatedText = Animated.Text;
|
|||
class PageHeader extends React.PureComponent {
|
||||
render() {
|
||||
const { title, onBackPressed } = this.props;
|
||||
const containerStyles = [
|
||||
pageHeaderStyle.container,
|
||||
{ height: APPBAR_HEIGHT }
|
||||
];
|
||||
const containerStyles = [pageHeaderStyle.container, { height: APPBAR_HEIGHT }];
|
||||
|
||||
return (
|
||||
<View style={containerStyles}>
|
||||
<View style={pageHeaderStyle.flexOne}>
|
||||
<View style={pageHeaderStyle.header}>
|
||||
<View style={pageHeaderStyle.title}>
|
||||
<AnimatedText
|
||||
numberOfLines={1}
|
||||
style={pageHeaderStyle.titleText}
|
||||
accessibilityTraits="header">
|
||||
<AnimatedText numberOfLines={1} style={pageHeaderStyle.titleText} accessibilityTraits="header">
|
||||
{title}
|
||||
</AnimatedText>
|
||||
</View>
|
||||
|
|
|
@ -19,7 +19,7 @@ class ProgressBar extends React.PureComponent {
|
|||
return new Error('progress should be between 0 and 100');
|
||||
}
|
||||
},
|
||||
style: PropTypes.any
|
||||
style: PropTypes.any,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -39,13 +39,15 @@ class ProgressBar extends React.PureComponent {
|
|||
borderRadius: borderRadius || defaultBorderRadius,
|
||||
flexDirection: 'row',
|
||||
height: height || defaultHeight,
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles}>
|
||||
<View style={{ backgroundColor: color, borderRadius: borderRadius || defaultBorderRadius, flex: currentProgress }} />
|
||||
<View style={{ backgroundColor: color, opacity: 0.2, flex: (100 - currentProgress) }} />
|
||||
<View
|
||||
style={{ backgroundColor: color, borderRadius: borderRadius || defaultBorderRadius, flex: currentProgress }}
|
||||
/>
|
||||
<View style={{ backgroundColor: color, opacity: 0.2, flex: 100 - currentProgress }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,14 +50,16 @@ export default class RelatedContent extends React.PureComponent<Props> {
|
|||
return (
|
||||
<View style={relatedContentStyle.container}>
|
||||
<Text style={relatedContentStyle.title}>Related Content</Text>
|
||||
{recommendedContent && recommendedContent.map(recommendedUri => (
|
||||
<FileListItem
|
||||
style={fileListStyle.item}
|
||||
key={recommendedUri}
|
||||
uri={recommendedUri}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })} />
|
||||
))}
|
||||
{recommendedContent &&
|
||||
recommendedContent.map(recommendedUri => (
|
||||
<FileListItem
|
||||
style={fileListStyle.item}
|
||||
key={recommendedUri}
|
||||
uri={recommendedUri}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })}
|
||||
/>
|
||||
))}
|
||||
{isSearching && <ActivityIndicator size="small" color={Colors.LbryGreen} style={relatedContentStyle.loading} />}
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -23,7 +23,10 @@ const makeSelect = () => {
|
|||
const perform = dispatch => ({
|
||||
claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
|
||||
clearError: reward => dispatch(doClaimRewardClearError(reward)),
|
||||
notify: data => dispatch(doToast(data))
|
||||
notify: data => dispatch(doToast(data)),
|
||||
});
|
||||
|
||||
export default connect(makeSelect, perform)(RewardCard);
|
||||
export default connect(
|
||||
makeSelect,
|
||||
perform
|
||||
)(RewardCard);
|
||||
|
|
|
@ -7,7 +7,7 @@ import Link from '../link';
|
|||
import rewardStyle from '../../styles/reward';
|
||||
|
||||
type Props = {
|
||||
canClaim: bool,
|
||||
canClaim: boolean,
|
||||
onClaimPress: object,
|
||||
reward: {
|
||||
id: string,
|
||||
|
@ -22,7 +22,7 @@ type Props = {
|
|||
|
||||
class RewardCard extends React.PureComponent<Props> {
|
||||
state = {
|
||||
claimStarted: false
|
||||
claimStarted: false,
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -40,13 +40,7 @@ class RewardCard extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
onClaimPress = () => {
|
||||
const {
|
||||
canClaim,
|
||||
claimReward,
|
||||
notify,
|
||||
reward,
|
||||
showVerification
|
||||
} = this.props;
|
||||
const { canClaim, claimReward, notify, reward, showVerification } = this.props;
|
||||
|
||||
if (!canClaim) {
|
||||
if (showVerification) {
|
||||
|
@ -59,37 +53,52 @@ class RewardCard extends React.PureComponent<Props> {
|
|||
this.setState({ claimStarted: true }, () => {
|
||||
claimReward(reward);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { canClaim, isPending, onClaimPress, reward } = this.props;
|
||||
const claimed = !!reward.transaction_id;
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={[rewardStyle.rewardCard, rewardStyle.row]} onPress={() => {
|
||||
if (!isPending && !claimed) {
|
||||
this.onClaimPress();
|
||||
}
|
||||
}}>
|
||||
<TouchableOpacity
|
||||
style={[rewardStyle.rewardCard, rewardStyle.row]}
|
||||
onPress={() => {
|
||||
if (!isPending && !claimed) {
|
||||
this.onClaimPress();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={rewardStyle.leftCol}>
|
||||
{!isPending && <TouchableOpacity onPress={() => {
|
||||
if (!claimed) {
|
||||
this.onClaimPress();
|
||||
}
|
||||
}}>
|
||||
{claimed && <Icon name={claimed ? "check-circle" : "circle"}
|
||||
style={claimed ? rewardStyle.claimed : (canClaim ? rewardStyle.unclaimed : rewardStyle.disabled)}
|
||||
size={20} />}
|
||||
</TouchableOpacity>}
|
||||
{!isPending && (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (!claimed) {
|
||||
this.onClaimPress();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{claimed && (
|
||||
<Icon
|
||||
name={claimed ? 'check-circle' : 'circle'}
|
||||
style={claimed ? rewardStyle.claimed : canClaim ? rewardStyle.unclaimed : rewardStyle.disabled}
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
|
||||
</View>
|
||||
<View style={rewardStyle.midCol}>
|
||||
<Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text>
|
||||
<Text style={rewardStyle.rewardDescription}>{reward.reward_description}</Text>
|
||||
{claimed && <Link style={rewardStyle.link}
|
||||
href={`https://explorer.lbry.com/tx/${reward.transaction_id}`}
|
||||
text={reward.transaction_id.substring(0, 7)}
|
||||
error={'The transaction URL could not be opened'} />}
|
||||
{claimed && (
|
||||
<Link
|
||||
style={rewardStyle.link}
|
||||
href={`https://explorer.lbry.com/tx/${reward.transaction_id}`}
|
||||
text={reward.transaction_id.substring(0, 7)}
|
||||
error={'The transaction URL could not be opened'}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={rewardStyle.rightCol}>
|
||||
<Text style={rewardStyle.rewardAmount}>{reward.reward_amount}</Text>
|
||||
|
@ -98,6 +107,6 @@ class RewardCard extends React.PureComponent<Props> {
|
|||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default RewardCard;
|
||||
|
|
|
@ -7,7 +7,7 @@ import RewardEnrolment from './view';
|
|||
const select = state => ({
|
||||
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
||||
fetching: selectFetchingRewards(state),
|
||||
user: selectUser(state)
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -16,4 +16,7 @@ const perform = dispatch => ({
|
|||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(RewardEnrolment);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(RewardEnrolment);
|
||||
|
|
|
@ -17,12 +17,12 @@ class RewardEnrolment extends React.Component {
|
|||
const { navigation, setClientSetting } = this.props;
|
||||
setClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED, true);
|
||||
navigation.navigate({ routeName: 'DiscoverStack' });
|
||||
}
|
||||
};
|
||||
|
||||
onEnrollPressed = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false }});
|
||||
}
|
||||
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false } });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
|
||||
|
@ -31,20 +31,20 @@ class RewardEnrolment extends React.Component {
|
|||
<View style={rewardStyle.enrollContainer} onPress>
|
||||
<View style={rewardStyle.summaryRow}>
|
||||
<Icon name="award" size={36} color={Colors.White} />
|
||||
<Text style={rewardStyle.summaryText}>
|
||||
{unclaimedRewardAmount} unclaimed credits
|
||||
</Text>
|
||||
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
|
||||
</View>
|
||||
|
||||
<View style={rewardStyle.onboarding}>
|
||||
<Text style={rewardStyle.enrollDescText}>LBRY credits allow you to purchase content, publish content, and influence the network. You can start earning credits by watching videos on LBRY.</Text>
|
||||
<Text style={rewardStyle.enrollDescText}>
|
||||
LBRY credits allow you to purchase content, publish content, and influence the network. You can start
|
||||
earning credits by watching videos on LBRY.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={rewardStyle.buttonRow}>
|
||||
<Link style={rewardStyle.notInterestedLink} text={"Not interested"} onPress={this.onNotInterestedPressed} />
|
||||
<Button style={rewardStyle.enrollButton} theme={"light"} text={"Enroll"} onPress={this.onEnrollPressed} />
|
||||
<Link style={rewardStyle.notInterestedLink} text={'Not interested'} onPress={this.onNotInterestedPressed} />
|
||||
<Button style={rewardStyle.enrollButton} theme={'light'} text={'Enroll'} onPress={this.onEnrollPressed} />
|
||||
</View>
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,12 +6,15 @@ import RewardSummary from './view';
|
|||
const select = state => ({
|
||||
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
||||
fetching: selectFetchingRewards(state),
|
||||
user: selectUser(state)
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
fetchRewards: () => dispatch(doRewardList()),
|
||||
notify: data => dispatch(doToast(data))
|
||||
notify: data => dispatch(doToast(data)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(RewardSummary);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(RewardSummary);
|
||||
|
|
|
@ -11,7 +11,7 @@ class RewardSummary extends React.Component {
|
|||
|
||||
state = {
|
||||
actionsLeft: 0,
|
||||
dismissed: false
|
||||
dismissed: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -42,14 +42,14 @@ class RewardSummary extends React.Component {
|
|||
this.props.notify({
|
||||
message: 'You can always claim your rewards from the Rewards page.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleSummaryPressed = () => {
|
||||
const { showVerification } = this.props;
|
||||
if (showVerification) {
|
||||
showVerification();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
|
||||
|
@ -58,10 +58,12 @@ class RewardSummary extends React.Component {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (this.state.dismissed ||
|
||||
(user && user.is_reward_approved) ||
|
||||
this.state.actionsLeft === 0 ||
|
||||
unclaimedRewardAmount === 0) {
|
||||
if (
|
||||
this.state.dismissed ||
|
||||
(user && user.is_reward_approved) ||
|
||||
this.state.actionsLeft === 0 ||
|
||||
unclaimedRewardAmount === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -69,11 +71,9 @@ class RewardSummary extends React.Component {
|
|||
<TouchableOpacity style={rewardStyle.summaryContainer} onPress={this.handleSummaryPressed}>
|
||||
<View style={rewardStyle.summaryRow}>
|
||||
<Icon name="award" size={36} color={Colors.White} />
|
||||
<Text style={rewardStyle.summaryText}>
|
||||
{unclaimedRewardAmount} unclaimed credits
|
||||
</Text>
|
||||
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
|
||||
</View>
|
||||
<Button style={rewardStyle.dismissButton} theme={"light"} text={"Dismiss"} onPress={this.onDismissPressed} />
|
||||
<Button style={rewardStyle.dismissButton} theme={'light'} text={'Dismiss'} onPress={this.onDismissPressed} />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { NativeModules } from 'react-native';
|
||||
import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
|
||||
import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
|
||||
import SearchInput from './view';
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -10,7 +10,10 @@ const perform = dispatch => ({
|
|||
}
|
||||
return dispatch(doSearch(search));
|
||||
},
|
||||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query, false))
|
||||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query, false)),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(SearchInput);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(SearchInput);
|
||||
|
|
|
@ -5,7 +5,7 @@ class SearchInput extends React.PureComponent {
|
|||
static INPUT_TIMEOUT = 500;
|
||||
|
||||
state = {
|
||||
changeTextTimeout: -1
|
||||
changeTextTimeout: -1,
|
||||
};
|
||||
|
||||
handleChangeText = text => {
|
||||
|
@ -21,7 +21,7 @@ class SearchInput extends React.PureComponent {
|
|||
search(text);
|
||||
}, SearchInput.INPUT_TIMEOUT);
|
||||
this.setState({ changeTextTimeout: timeout });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { style, value } = this.props;
|
||||
|
@ -32,7 +32,8 @@ class SearchInput extends React.PureComponent {
|
|||
placeholder="Search"
|
||||
underlineColorAndroid="transparent"
|
||||
value={value}
|
||||
onChangeText={text => this.handleChangeText(text)} />
|
||||
onChangeText={text => this.handleChangeText(text)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,13 @@ import { connect } from 'react-redux';
|
|||
import SearchRightHeaderIcon from './view';
|
||||
import { ACTIONS } from 'lbry-redux';
|
||||
const perform = dispatch => ({
|
||||
clearQuery: () => dispatch({
|
||||
type: ACTIONS.HISTORY_NAVIGATE
|
||||
})
|
||||
clearQuery: () =>
|
||||
dispatch({
|
||||
type: ACTIONS.HISTORY_NAVIGATE,
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(SearchRightHeaderIcon);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(SearchRightHeaderIcon);
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import Feather from "react-native-vector-icons/Feather";
|
||||
import Feather from 'react-native-vector-icons/Feather';
|
||||
|
||||
class SearchRightHeaderIcon extends React.PureComponent {
|
||||
|
||||
clearAndGoBack() {
|
||||
const { navigation } = this.props;
|
||||
this.props.clearQuery();
|
||||
navigation.dispatch(NavigationActions.back())
|
||||
navigation.dispatch(NavigationActions.back());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import React from 'react';
|
||||
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
Switch,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Platform, Switch, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { formatBytes } from '../../utils/helper';
|
||||
import Colors from '../../styles/colors';
|
||||
import storageStatsStyle from '../../styles/storageStats';
|
||||
|
@ -23,15 +16,20 @@ class StorageStatsCard extends React.PureComponent {
|
|||
totalVideoPercent: 0,
|
||||
totalOtherBytes: 0,
|
||||
totalOtherPercent: 0,
|
||||
showStats: false
|
||||
showStats: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// calculate total bytes
|
||||
const { fileInfos } = this.props;
|
||||
|
||||
let totalBytes = 0, totalAudioBytes = 0, totalImageBytes = 0, totalVideoBytes = 0;
|
||||
let totalAudioPercent = 0, totalImagePercent = 0, totalVideoPercent = 0;
|
||||
let totalBytes = 0,
|
||||
totalAudioBytes = 0,
|
||||
totalImageBytes = 0,
|
||||
totalVideoBytes = 0;
|
||||
let totalAudioPercent = 0,
|
||||
totalImagePercent = 0,
|
||||
totalVideoPercent = 0;
|
||||
|
||||
fileInfos.forEach(fileInfo => {
|
||||
if (fileInfo.completed) {
|
||||
|
@ -59,9 +57,10 @@ class StorageStatsCard extends React.PureComponent {
|
|||
totalVideoBytes,
|
||||
totalVideoPercent,
|
||||
totalOtherBytes: totalBytes - (totalAudioBytes + totalImageBytes + totalVideoBytes),
|
||||
totalOtherPercent: (100 - (parseFloat(totalAudioPercent) +
|
||||
parseFloat(totalImagePercent) +
|
||||
parseFloat(totalVideoPercent))).toFixed(2)
|
||||
totalOtherPercent: (
|
||||
100 -
|
||||
(parseFloat(totalAudioPercent) + parseFloat(totalImagePercent) + parseFloat(totalVideoPercent))
|
||||
).toFixed(2),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,50 +81,52 @@ class StorageStatsCard extends React.PureComponent {
|
|||
<Switch
|
||||
style={storageStatsStyle.statsToggle}
|
||||
value={this.state.showStats}
|
||||
onValueChange={(value) => this.setState({ showStats: value })} />
|
||||
onValueChange={value => this.setState({ showStats: value })}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{this.state.showStats &&
|
||||
<View>
|
||||
<View style={storageStatsStyle.distributionBar}>
|
||||
<View style={[storageStatsStyle.audioDistribution, { flex: parseFloat(this.state.totalAudioPercent) }]} />
|
||||
<View style={[storageStatsStyle.imageDistribution, { flex: parseFloat(this.state.totalImagePercent) }]} />
|
||||
<View style={[storageStatsStyle.videoDistribution, { flex: parseFloat(this.state.totalVideoPercent) }]} />
|
||||
<View style={[storageStatsStyle.otherDistribution, { flex: parseFloat(this.state.totalOtherPercent) }]} />
|
||||
{this.state.showStats && (
|
||||
<View>
|
||||
<View style={storageStatsStyle.distributionBar}>
|
||||
<View style={[storageStatsStyle.audioDistribution, { flex: parseFloat(this.state.totalAudioPercent) }]} />
|
||||
<View style={[storageStatsStyle.imageDistribution, { flex: parseFloat(this.state.totalImagePercent) }]} />
|
||||
<View style={[storageStatsStyle.videoDistribution, { flex: parseFloat(this.state.totalVideoPercent) }]} />
|
||||
<View style={[storageStatsStyle.otherDistribution, { flex: parseFloat(this.state.totalOtherPercent) }]} />
|
||||
</View>
|
||||
<View style={storageStatsStyle.legend}>
|
||||
{this.state.totalAudioBytes > 0 && (
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.audioDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Audio</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalAudioBytes, 2)}</Text>
|
||||
</View>
|
||||
)}
|
||||
{this.state.totalImageBytes > 0 && (
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.imageDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Images</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalImageBytes, 2)}</Text>
|
||||
</View>
|
||||
)}
|
||||
{this.state.totalVideoBytes > 0 && (
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.videoDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Videos</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalVideoBytes, 2)}</Text>
|
||||
</View>
|
||||
)}
|
||||
{this.state.totalOtherBytes > 0 && (
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.otherDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Other</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalOtherBytes, 2)}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View style={storageStatsStyle.legend}>
|
||||
{this.state.totalAudioBytes > 0 &&
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.audioDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Audio</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalAudioBytes, 2)}</Text>
|
||||
</View>
|
||||
}
|
||||
{this.state.totalImageBytes > 0 &&
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.imageDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Images</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalImageBytes, 2)}</Text>
|
||||
</View>
|
||||
}
|
||||
{this.state.totalVideoBytes > 0 &&
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.videoDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Videos</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalVideoBytes, 2)}</Text>
|
||||
</View>
|
||||
}
|
||||
{this.state.totalOtherBytes > 0 &&
|
||||
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
|
||||
<View style={[storageStatsStyle.legendBox, storageStatsStyle.otherDistribution]} />
|
||||
<Text style={storageStatsStyle.legendText}>Other</Text>
|
||||
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalOtherBytes, 2)}</Text>
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doChannelSubscribe,
|
||||
doChannelUnsubscribe,
|
||||
selectSubscriptions,
|
||||
makeSelectIsSubscribed,
|
||||
} from 'lbryinc';
|
||||
import { doChannelSubscribe, doChannelUnsubscribe, selectSubscriptions, makeSelectIsSubscribed } from 'lbryinc';
|
||||
import { doToast } from 'lbry-redux';
|
||||
import SubscribeButton from './view';
|
||||
|
||||
|
|
|
@ -6,14 +6,7 @@ import Colors from 'styles/colors';
|
|||
|
||||
class SubscribeButton extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
uri,
|
||||
isSubscribed,
|
||||
doChannelSubscribe,
|
||||
doChannelUnsubscribe,
|
||||
style,
|
||||
hideText
|
||||
} = this.props;
|
||||
const { uri, isSubscribed, doChannelSubscribe, doChannelUnsubscribe, style, hideText } = this.props;
|
||||
|
||||
let styles = [];
|
||||
if (style) {
|
||||
|
@ -32,8 +25,8 @@ class SubscribeButton extends React.PureComponent {
|
|||
return (
|
||||
<Button
|
||||
style={styles}
|
||||
theme={"light"}
|
||||
icon={isSubscribed ? "heart-broken" : "heart"}
|
||||
theme={'light'}
|
||||
icon={isSubscribed ? 'heart-broken' : 'heart'}
|
||||
iconColor={iconColor}
|
||||
solid={isSubscribed ? false : true}
|
||||
text={hideText ? null : subscriptionLabel}
|
||||
|
@ -42,7 +35,8 @@ class SubscribeButton extends React.PureComponent {
|
|||
channelName: claimName,
|
||||
uri: normalizeURI(uri),
|
||||
});
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@ class SubscribeNotificationButton extends React.PureComponent {
|
|||
doToast,
|
||||
enabledChannelNotifications,
|
||||
isSubscribed,
|
||||
style
|
||||
style,
|
||||
} = this.props;
|
||||
|
||||
if (!isSubscribed) {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
let styles = [];
|
||||
|
@ -36,8 +36,8 @@ class SubscribeNotificationButton extends React.PureComponent {
|
|||
return (
|
||||
<Button
|
||||
style={styles}
|
||||
theme={"light"}
|
||||
icon={shouldNotify ? "bell-slash" : "bell"}
|
||||
theme={'light'}
|
||||
icon={shouldNotify ? 'bell-slash' : 'bell'}
|
||||
solid={true}
|
||||
onPress={() => {
|
||||
if (shouldNotify) {
|
||||
|
@ -47,7 +47,8 @@ class SubscribeNotificationButton extends React.PureComponent {
|
|||
doChannelSubscriptionEnableNotifications(name);
|
||||
doToast({ message: 'You will receive all notifications for new content.' });
|
||||
}
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,19 +14,19 @@ class SuggestedSubscriptionItem extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
uriForClaim = (claim) => {
|
||||
uriForClaim = claim => {
|
||||
const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = claim;
|
||||
const uriParams = {};
|
||||
const uriParams = {};
|
||||
|
||||
// This is unfortunate
|
||||
// https://github.com/lbryio/lbry/issues/1159
|
||||
const name = claimName || claimNameDownloaded;
|
||||
uriParams.contentName = name;
|
||||
uriParams.claimId = claimId;
|
||||
const uri = buildURI(uriParams);
|
||||
// This is unfortunate
|
||||
// https://github.com/lbryio/lbry/issues/1159
|
||||
const name = claimName || claimNameDownloaded;
|
||||
uriParams.contentName = name;
|
||||
uriParams.claimId = claimId;
|
||||
const uri = buildURI(uriParams);
|
||||
|
||||
return uri;
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { categoryLink, fetching, obscureNsfw, claims, navigation } = this.props;
|
||||
|
@ -46,23 +46,26 @@ class SuggestedSubscriptionItem extends React.PureComponent {
|
|||
style={subscriptionsStyle.compactMainFileItem}
|
||||
mediaStyle={subscriptionsStyle.fileItemMedia}
|
||||
uri={this.uriForClaim(claims[0])}
|
||||
navigation={navigation} />
|
||||
{(claims.length > 1) &&
|
||||
<FlatList style={subscriptionsStyle.compactItems}
|
||||
horizontal={true}
|
||||
renderItem={ ({item}) => (
|
||||
navigation={navigation}
|
||||
/>
|
||||
{claims.length > 1 && (
|
||||
<FlatList
|
||||
style={subscriptionsStyle.compactItems}
|
||||
horizontal={true}
|
||||
renderItem={({ item }) => (
|
||||
<FileItem
|
||||
style={subscriptionsStyle.compactFileItem}
|
||||
mediaStyle={subscriptionsStyle.compactFileItemMedia}
|
||||
key={item}
|
||||
uri={normalizeURI(item)}
|
||||
navigation={navigation}
|
||||
compactView={true} />
|
||||
)
|
||||
}
|
||||
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>}
|
||||
compactView={true}
|
||||
/>
|
||||
)}
|
||||
data={claims.slice(1, 4).map(claim => this.uriForClaim(claim))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,30 +22,29 @@ class SuggestedSubscriptions extends React.PureComponent {
|
|||
}
|
||||
|
||||
return suggested ? (
|
||||
<SectionList style={subscriptionsStyle.scrollContainer}
|
||||
renderItem={ ({item, index, section}) => (
|
||||
<SuggestedSubscriptionItem
|
||||
key={item}
|
||||
categoryLink={normalizeURI(item)}
|
||||
navigation={navigation} />
|
||||
)
|
||||
}
|
||||
renderSectionHeader={
|
||||
({section: {title}}) => {
|
||||
const titleParts = title.split(';');
|
||||
const channelName = titleParts[0];
|
||||
const channelUri = normalizeURI(titleParts[1]);
|
||||
return (
|
||||
<View style={subscriptionsStyle.titleRow}>
|
||||
<Link style={subscriptionsStyle.channelTitle} text={channelName} onPress={() => {
|
||||
<SectionList
|
||||
style={subscriptionsStyle.scrollContainer}
|
||||
renderItem={({ item, index, section }) => (
|
||||
<SuggestedSubscriptionItem key={item} categoryLink={normalizeURI(item)} navigation={navigation} />
|
||||
)}
|
||||
renderSectionHeader={({ section: { title } }) => {
|
||||
const titleParts = title.split(';');
|
||||
const channelName = titleParts[0];
|
||||
const channelUri = normalizeURI(titleParts[1]);
|
||||
return (
|
||||
<View style={subscriptionsStyle.titleRow}>
|
||||
<Link
|
||||
style={subscriptionsStyle.channelTitle}
|
||||
text={channelName}
|
||||
onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(channelUri));
|
||||
}} />
|
||||
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
sections={suggested.map(({ uri, label }) => ({ title: (label + ';' + uri), data: [uri] }))}
|
||||
}}
|
||||
/>
|
||||
<SubscribeButton style={subscriptionsStyle.subscribeButton} uri={channelUri} name={channelName} />
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
sections={suggested.map(({ uri, label }) => ({ title: label + ';' + uri, data: [uri] }))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>
|
||||
) : null;
|
||||
|
|
|
@ -8,4 +8,7 @@ const select = state => ({
|
|||
myClaims: selectAllMyClaimsByOutpoint(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(TransactionList);
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(TransactionList);
|
||||
|
|
|
@ -25,20 +25,25 @@ class TransactionListItem extends React.PureComponent {
|
|||
<Link
|
||||
style={transactionListStyle.link}
|
||||
onPress={() => navigateToUri(navigation, buildURI({ claimName: name, claimId }))}
|
||||
text={name} />
|
||||
text={name}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={transactionListStyle.col}>
|
||||
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>{formatCredits(amount, 8)}</Text>
|
||||
{ fee !== 0 && (<Text style={[transactionListStyle.amount, transactionListStyle.text]}>fee {formatCredits(fee, 8)}</Text>) }
|
||||
{fee !== 0 && (
|
||||
<Text style={[transactionListStyle.amount, transactionListStyle.text]}>fee {formatCredits(fee, 8)}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View style={transactionListStyle.row}>
|
||||
<View style={transactionListStyle.col}>
|
||||
<Link style={transactionListStyle.smallLink}
|
||||
text={txid.substring(0, 8)}
|
||||
href={`https://explorer.lbry.com/tx/${txid}`}
|
||||
error={'The transaction URL could not be opened'} />
|
||||
<Link
|
||||
style={transactionListStyle.smallLink}
|
||||
text={txid.substring(0, 8)}
|
||||
href={`https://explorer.lbry.com/tx/${txid}`}
|
||||
error={'The transaction URL could not be opened'}
|
||||
/>
|
||||
</View>
|
||||
<View style={transactionListStyle.col}>
|
||||
{date ? (
|
||||
|
|
|
@ -17,4 +17,7 @@ const perform = dispatch => ({
|
|||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(TransactionListRecent);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(TransactionListRecent);
|
||||
|
|
|
@ -27,14 +27,9 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
|||
<View style={walletStyle.transactionsCard}>
|
||||
<View style={[walletStyle.row, walletStyle.transactionsHeader]}>
|
||||
<Text style={walletStyle.transactionsTitle}>Recent Transactions</Text>
|
||||
<Link style={walletStyle.link}
|
||||
navigation={navigation}
|
||||
text={'View All'}
|
||||
href={'#TransactionHistory'} />
|
||||
<Link style={walletStyle.link} navigation={navigation} text={'View All'} href={'#TransactionHistory'} />
|
||||
</View>
|
||||
{fetchingTransactions && (
|
||||
<Text style={walletStyle.infoText}>Fetching transactions...</Text>
|
||||
)}
|
||||
{fetchingTransactions && <Text style={walletStyle.infoText}>Fetching transactions...</Text>}
|
||||
{!fetchingTransactions && (
|
||||
<TransactionList
|
||||
navigation={navigation}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
doUpdateSearchQuery,
|
||||
selectSearchState as selectSearch,
|
||||
selectSearchValue,
|
||||
selectSearchSuggestions
|
||||
selectSearchSuggestions,
|
||||
} from 'lbry-redux';
|
||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||
import UriBar from './view';
|
||||
|
@ -15,7 +15,7 @@ const select = state => {
|
|||
...searchState,
|
||||
query: selectSearchValue(state),
|
||||
currentRoute: selectCurrentRoute(state),
|
||||
suggestions: selectSearchSuggestions(state)
|
||||
suggestions: selectSearchSuggestions(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -23,4 +23,7 @@ const perform = dispatch => ({
|
|||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UriBar);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(UriBar);
|
||||
|
|
|
@ -13,16 +13,16 @@ class UriBarItem extends React.PureComponent {
|
|||
let icon;
|
||||
switch (type) {
|
||||
case SEARCH_TYPES.CHANNEL:
|
||||
icon = <Icon name="at" size={18} />
|
||||
icon = <Icon name="at" size={18} />;
|
||||
break;
|
||||
|
||||
case SEARCH_TYPES.SEARCH:
|
||||
icon = <Icon name="search" size={18} />
|
||||
icon = <Icon name="search" size={18} />;
|
||||
break;
|
||||
|
||||
case SEARCH_TYPES.FILE:
|
||||
default:
|
||||
icon = <Icon name="file" size={18} />
|
||||
icon = <Icon name="file" size={18} />;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,9 @@ class UriBarItem extends React.PureComponent {
|
|||
<TouchableOpacity style={uriBarStyle.item} onPress={onPress}>
|
||||
{icon}
|
||||
<View style={uriBarStyle.itemContent}>
|
||||
<Text style={uriBarStyle.itemText} numberOfLines={1}>{shorthand || value} - {type === SEARCH_TYPES.SEARCH ? 'Search' : value}</Text>
|
||||
<Text style={uriBarStyle.itemText} numberOfLines={1}>
|
||||
{shorthand || value} - {type === SEARCH_TYPES.SEARCH ? 'Search' : value}
|
||||
</Text>
|
||||
<Text style={uriBarStyle.itemDesc} numberOfLines={1}>
|
||||
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
|
||||
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
|
||||
|
@ -38,7 +40,7 @@ class UriBarItem extends React.PureComponent {
|
|||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class UriBar extends React.PureComponent {
|
|||
|
||||
keyboardDidHideListener = null;
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||
this.setSelection();
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class UriBar extends React.PureComponent {
|
|||
inputText: null,
|
||||
focused: false,
|
||||
// TODO: Add a setting to enable / disable direct search?
|
||||
directSearch: true
|
||||
directSearch: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -66,15 +66,14 @@ class UriBar extends React.PureComponent {
|
|||
if (onSearchSubmitted) {
|
||||
onSearchSubmitted(text);
|
||||
} else {
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text }});
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
|
||||
}
|
||||
}
|
||||
|
||||
}, UriBar.INPUT_TIMEOUT);
|
||||
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
|
||||
}
|
||||
};
|
||||
|
||||
handleItemPress = (item) => {
|
||||
handleItemPress = item => {
|
||||
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
|
||||
const { type, value } = item;
|
||||
|
||||
|
@ -89,23 +88,23 @@ class UriBar extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value } });
|
||||
} else {
|
||||
const uri = normalizeURI(value);
|
||||
navigateToUri(navigation, uri);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_keyboardDidHide = () => {
|
||||
if (this.textInput) {
|
||||
this.textInput.blur();
|
||||
}
|
||||
this.setState({ focused: false });
|
||||
}
|
||||
};
|
||||
|
||||
setSelection() {
|
||||
if (this.textInput) {
|
||||
this.textInput.setNativeProps({ selection: { start: 0, end: 0 }});
|
||||
this.textInput.setNativeProps({ selection: { start: 0, end: 0 } });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,17 +126,17 @@ class UriBar extends React.PureComponent {
|
|||
}
|
||||
|
||||
// Open the search page with the query populated
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText }});
|
||||
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText } });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onSearchPageBlurred() {
|
||||
this.setState({ currenValueSet: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigation, suggestions, query, value } = this.props;
|
||||
const { navigation, suggestions, query, value } = this.props;
|
||||
if (this.state.currentValue === null) {
|
||||
this.setState({ currentValue: value });
|
||||
}
|
||||
|
@ -155,39 +154,46 @@ class UriBar extends React.PureComponent {
|
|||
size={24}
|
||||
style={uriBarStyle.drawerMenuButton}
|
||||
iconStyle={discoverStyle.drawerHamburger}
|
||||
onPress={() => navigation.openDrawer() } />
|
||||
<TextInput ref={(ref) => { this.textInput = ref }}
|
||||
style={uriBarStyle.uriText}
|
||||
onLayout={() => { this.setSelection(); }}
|
||||
selectTextOnFocus={true}
|
||||
placeholder={'Search movies, music, and more'}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
clearButtonMode={'while-editing'}
|
||||
value={this.state.currentValue}
|
||||
returnKeyType={'go'}
|
||||
inlineImageLeft={'baseline_search_black_24'}
|
||||
inlineImagePadding={16}
|
||||
onFocus={() => this.setState({ focused: true })}
|
||||
onBlur={() => {
|
||||
this.setState({ focused: false });
|
||||
this.setSelection();
|
||||
}}
|
||||
onChangeText={this.handleChangeText}
|
||||
onSubmitEditing={this.handleSubmitEditing}/>
|
||||
{(this.state.focused && !this.state.directSearch) && (
|
||||
<View style={uriBarStyle.suggestions}>
|
||||
<FlatList style={uriBarStyle.suggestionList}
|
||||
data={suggestions}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={(item, value) => item.value}
|
||||
renderItem={({item}) => (
|
||||
<UriBarItem
|
||||
item={item}
|
||||
navigation={navigation}
|
||||
onPress={() => this.handleItemPress(item)}
|
||||
/>)} />
|
||||
</View>)}
|
||||
onPress={() => navigation.openDrawer()}
|
||||
/>
|
||||
<TextInput
|
||||
ref={ref => {
|
||||
this.textInput = ref;
|
||||
}}
|
||||
style={uriBarStyle.uriText}
|
||||
onLayout={() => {
|
||||
this.setSelection();
|
||||
}}
|
||||
selectTextOnFocus={true}
|
||||
placeholder={'Search movies, music, and more'}
|
||||
underlineColorAndroid={'transparent'}
|
||||
numberOfLines={1}
|
||||
clearButtonMode={'while-editing'}
|
||||
value={this.state.currentValue}
|
||||
returnKeyType={'go'}
|
||||
inlineImageLeft={'baseline_search_black_24'}
|
||||
inlineImagePadding={16}
|
||||
onFocus={() => this.setState({ focused: true })}
|
||||
onBlur={() => {
|
||||
this.setState({ focused: false });
|
||||
this.setSelection();
|
||||
}}
|
||||
onChangeText={this.handleChangeText}
|
||||
onSubmitEditing={this.handleSubmitEditing}
|
||||
/>
|
||||
{this.state.focused && !this.state.directSearch && (
|
||||
<View style={uriBarStyle.suggestions}>
|
||||
<FlatList
|
||||
style={uriBarStyle.suggestionList}
|
||||
data={suggestions}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
keyExtractor={(item, value) => item.value}
|
||||
renderItem={({ item }) => (
|
||||
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doCheckAddressIsMine,
|
||||
doGetNewAddress,
|
||||
selectReceiveAddress,
|
||||
selectGettingNewAddress,
|
||||
} from 'lbry-redux';
|
||||
import { doCheckAddressIsMine, doGetNewAddress, selectReceiveAddress, selectGettingNewAddress } from 'lbry-redux';
|
||||
import WalletAddress from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -17,4 +12,7 @@ const perform = dispatch => ({
|
|||
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(WalletAddress);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(WalletAddress);
|
||||
|
|
|
@ -28,16 +28,20 @@ class WalletAddress extends React.PureComponent<Props> {
|
|||
return (
|
||||
<View style={walletStyle.card}>
|
||||
<Text style={walletStyle.title}>Receive Credits</Text>
|
||||
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>Use this wallet address to receive credits sent by another user (or yourself).</Text>
|
||||
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>
|
||||
Use this wallet address to receive credits sent by another user (or yourself).
|
||||
</Text>
|
||||
<Address address={receiveAddress} style={walletStyle.bottomMarginSmall} />
|
||||
<Button style={[walletStyle.button, walletStyle.bottomMarginLarge]}
|
||||
icon={'sync'}
|
||||
text={'Get new address'}
|
||||
onPress={getNewAddress}
|
||||
disabled={gettingNewAddress}
|
||||
/>
|
||||
<Button
|
||||
style={[walletStyle.button, walletStyle.bottomMarginLarge]}
|
||||
icon={'sync'}
|
||||
text={'Get new address'}
|
||||
onPress={getNewAddress}
|
||||
disabled={gettingNewAddress}
|
||||
/>
|
||||
<Text style={walletStyle.smallText}>
|
||||
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
|
||||
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple
|
||||
addresses can be helpful for keeping track of incoming payments from multiple sources.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -6,4 +6,7 @@ const select = state => ({
|
|||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(WalletBalance);
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(WalletBalance);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { Lbry, formatCredits } from 'lbry-redux'
|
||||
import { Lbry, formatCredits } from 'lbry-redux';
|
||||
import Address from 'component/address';
|
||||
import Button from 'component/button';
|
||||
import walletStyle from 'styles/wallet';
|
||||
|
@ -19,7 +19,7 @@ class WalletBalance extends React.PureComponent<Props> {
|
|||
<Text style={walletStyle.balanceTitle}>Balance</Text>
|
||||
<Text style={walletStyle.balanceCaption}>You currently have</Text>
|
||||
<Text style={walletStyle.balance}>
|
||||
{(balance || balance === 0) && (formatCredits(parseFloat(balance), 2) + ' LBC')}
|
||||
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
doSendDraftTransaction,
|
||||
selectDraftTransaction,
|
||||
selectDraftTransactionError,
|
||||
selectBalance
|
||||
selectBalance,
|
||||
} from 'lbry-redux';
|
||||
import WalletSend from './view';
|
||||
|
||||
|
@ -16,7 +16,10 @@ const select = state => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
sendToAddress: (address, amount) => dispatch(doSendDraftTransaction(address, amount)),
|
||||
notify: (data) => dispatch(doToast(data))
|
||||
notify: data => dispatch(doToast(data)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(WalletSend);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(WalletSend);
|
||||
|
|
|
@ -22,7 +22,7 @@ class WalletSend extends React.PureComponent<Props> {
|
|||
amount: null,
|
||||
address: null,
|
||||
addressChanged: false,
|
||||
addressValid: false
|
||||
addressValid: false,
|
||||
};
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
|
@ -51,18 +51,18 @@ class WalletSend extends React.PureComponent<Props> {
|
|||
|
||||
if (amount && address) {
|
||||
// Show confirmation before send
|
||||
Alert.alert(
|
||||
'Send LBC',
|
||||
`Are you sure you want to send ${amount} LBC to ${address}?`,
|
||||
[
|
||||
Alert.alert('Send LBC', `Are you sure you want to send ${amount} LBC to ${address}?`, [
|
||||
{ text: 'No' },
|
||||
{ text: 'Yes', onPress: () => {
|
||||
sendToAddress(address, parseFloat(amount));
|
||||
this.setState({ address: null, amount: null });
|
||||
}}
|
||||
{
|
||||
text: 'Yes',
|
||||
onPress: () => {
|
||||
sendToAddress(address, parseFloat(amount));
|
||||
this.setState({ address: null, amount: null });
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAddressInputBlur = () => {
|
||||
if (this.state.addressChanged && !this.state.addressValid) {
|
||||
|
@ -71,52 +71,58 @@ class WalletSend extends React.PureComponent<Props> {
|
|||
message: 'The recipient address is not a valid LBRY address.',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleAddressInputSubmit = () => {
|
||||
if (this.amountInput) {
|
||||
this.amountInput.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { balance } = this.props;
|
||||
const canSend = this.state.address &&
|
||||
this.state.amount > 0 &&
|
||||
this.state.address.trim().length > 0;
|
||||
const canSend = this.state.address && this.state.amount > 0 && this.state.address.trim().length > 0;
|
||||
|
||||
return (
|
||||
<View style={walletStyle.card}>
|
||||
<Text style={walletStyle.title}>Send Credits</Text>
|
||||
<Text style={walletStyle.text}>Recipient address</Text>
|
||||
<View style={[walletStyle.row, walletStyle.bottomMarginMedium]}>
|
||||
<TextInput onChangeText={value => this.setState({
|
||||
address: value,
|
||||
addressChanged: true,
|
||||
addressValid: (value.trim().length == 0 || regexAddress.test(value))
|
||||
})}
|
||||
onBlur={this.handleAddressInputBlur}
|
||||
onSubmitEditing={this.handleAddressInputSubmit}
|
||||
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
|
||||
value={this.state.address}
|
||||
returnKeyType={'next'}
|
||||
style={[walletStyle.input, walletStyle.addressInput, walletStyle.bottomMarginMedium]} />
|
||||
<TextInput
|
||||
onChangeText={value =>
|
||||
this.setState({
|
||||
address: value,
|
||||
addressChanged: true,
|
||||
addressValid: value.trim().length == 0 || regexAddress.test(value),
|
||||
})
|
||||
}
|
||||
onBlur={this.handleAddressInputBlur}
|
||||
onSubmitEditing={this.handleAddressInputSubmit}
|
||||
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
|
||||
value={this.state.address}
|
||||
returnKeyType={'next'}
|
||||
style={[walletStyle.input, walletStyle.addressInput, walletStyle.bottomMarginMedium]}
|
||||
/>
|
||||
</View>
|
||||
<Text style={walletStyle.text}>Amount</Text>
|
||||
<View style={walletStyle.row}>
|
||||
<View style={walletStyle.amountRow}>
|
||||
<TextInput ref={ref => this.amountInput = ref}
|
||||
onChangeText={value => this.setState({amount: value})}
|
||||
keyboardType={'numeric'}
|
||||
placeholder={'0'}
|
||||
value={this.state.amount}
|
||||
style={[walletStyle.input, walletStyle.amountInput]} />
|
||||
<TextInput
|
||||
ref={ref => (this.amountInput = ref)}
|
||||
onChangeText={value => this.setState({ amount: value })}
|
||||
keyboardType={'numeric'}
|
||||
placeholder={'0'}
|
||||
value={this.state.amount}
|
||||
style={[walletStyle.input, walletStyle.amountInput]}
|
||||
/>
|
||||
<Text style={[walletStyle.text, walletStyle.currency]}>LBC</Text>
|
||||
</View>
|
||||
<Button text={'Send'}
|
||||
style={[walletStyle.button, walletStyle.sendButton]}
|
||||
disabled={!canSend}
|
||||
onPress={this.handleSend} />
|
||||
<Button
|
||||
text={'Send'}
|
||||
style={[walletStyle.button, walletStyle.sendButton]}
|
||||
disabled={!canSend}
|
||||
onPress={this.handleSend}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -7,4 +7,7 @@ const select = state => ({
|
|||
deviceWalletSynced: makeSelectClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED)(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(WalletSyncDriver);
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(WalletSyncDriver);
|
||||
|
|
|
@ -8,7 +8,7 @@ class WalletSyncDriver extends React.PureComponent<Props> {
|
|||
onEnableSyncPressed = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: true } });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { deviceWalletSynced } = this.props;
|
||||
|
@ -17,14 +17,29 @@ class WalletSyncDriver extends React.PureComponent<Props> {
|
|||
<View style={walletStyle.syncDriverCard}>
|
||||
<View style={walletStyle.syncDriverRow}>
|
||||
<Text style={walletStyle.syncDriverTitle}>Wallet sync is {deviceWalletSynced ? 'on' : 'off'}.</Text>
|
||||
{!deviceWalletSynced &&
|
||||
<Link text="Sync FAQ" href="https://lbry.com/faq/how-to-backup-wallet#sync" style={walletStyle.syncDriverText} />}
|
||||
{!deviceWalletSynced && (
|
||||
<Link
|
||||
text="Sync FAQ"
|
||||
href="https://lbry.com/faq/how-to-backup-wallet#sync"
|
||||
style={walletStyle.syncDriverText}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{!deviceWalletSynced &&
|
||||
<View style={walletStyle.actionRow}>
|
||||
<Button style={walletStyle.enrollButton} theme={"light"} text={"Enable"} onPress={this.onEnableSyncPressed} />
|
||||
<Link text="Manual backup" href="https://lbry.com/faq/how-to-backup-wallet#android" style={walletStyle.syncDriverText} />
|
||||
</View>}
|
||||
{!deviceWalletSynced && (
|
||||
<View style={walletStyle.actionRow}>
|
||||
<Button
|
||||
style={walletStyle.enrollButton}
|
||||
theme={'light'}
|
||||
text={'Enable'}
|
||||
onPress={this.onEnableSyncPressed}
|
||||
/>
|
||||
<Link
|
||||
text="Manual backup"
|
||||
href="https://lbry.com/faq/how-to-backup-wallet#android"
|
||||
style={walletStyle.syncDriverText}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,73 +1,72 @@
|
|||
const Constants = {
|
||||
FIRST_RUN_PAGE_WELCOME: "welcome",
|
||||
FIRST_RUN_PAGE_EMAIL_COLLECT: "email-collect",
|
||||
FIRST_RUN_PAGE_EMAIL_VERIFY:"email-verify",
|
||||
FIRST_RUN_PAGE_WALLET: "wallet",
|
||||
FIRST_RUN_PAGE_SKIP_ACCOUNT: "skip-account",
|
||||
FIRST_RUN_PAGE_WELCOME: 'welcome',
|
||||
FIRST_RUN_PAGE_EMAIL_COLLECT: 'email-collect',
|
||||
FIRST_RUN_PAGE_EMAIL_VERIFY: 'email-verify',
|
||||
FIRST_RUN_PAGE_WALLET: 'wallet',
|
||||
FIRST_RUN_PAGE_SKIP_ACCOUNT: 'skip-account',
|
||||
|
||||
VERIFY_PAGE_EMAIL: "email-verify",
|
||||
VERIFY_PAGE_PHONE_NUMBER: "phone-number-verify",
|
||||
VERIFY_PAGE_EMAIL: 'email-verify',
|
||||
VERIFY_PAGE_PHONE_NUMBER: 'phone-number-verify',
|
||||
|
||||
PHASE_COLLECTION: "collection",
|
||||
PHASE_VERIFICATION: "verification",
|
||||
PHASE_COLLECTION: 'collection',
|
||||
PHASE_VERIFICATION: 'verification',
|
||||
|
||||
CONTENT_TAB: "content",
|
||||
ABOUT_TAB: "about",
|
||||
CONTENT_TAB: 'content',
|
||||
ABOUT_TAB: 'about',
|
||||
|
||||
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
|
||||
KEY_FIRST_RUN_PASSWORD: "firstRunPassword",
|
||||
KEY_FIRST_USER_AUTH: "firstUserAuth",
|
||||
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
|
||||
KEY_EMAIL_VERIFY_PENDING: "emailVerifyPending",
|
||||
KEY_FIRST_RUN_EMAIL: 'firstRunEmail',
|
||||
KEY_FIRST_RUN_PASSWORD: 'firstRunPassword',
|
||||
KEY_FIRST_USER_AUTH: 'firstUserAuth',
|
||||
KEY_SHOULD_VERIFY_EMAIL: 'shouldVerifyEmail',
|
||||
KEY_EMAIL_VERIFY_PENDING: 'emailVerifyPending',
|
||||
|
||||
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks",
|
||||
SETTING_SUBSCRIPTIONS_VIEW_MODE: "subscriptionsViewMode",
|
||||
SETTING_RATING_REMINDER_LAST_SHOWN: "ratingReminderLastShown",
|
||||
SETTING_RATING_REMINDER_DISABLED: "ratingReminderDisabled",
|
||||
SETTING_BACKUP_DISMISSED: "backupDismissed",
|
||||
SETTING_REWARDS_NOT_INTERESTED: "rewardsNotInterested",
|
||||
SETTING_DEVICE_WALLET_SYNCED: "deviceWalletSynced",
|
||||
SETTING_ALPHA_UNDERSTANDS_RISKS: 'alphaUnderstandRisks',
|
||||
SETTING_SUBSCRIPTIONS_VIEW_MODE: 'subscriptionsViewMode',
|
||||
SETTING_RATING_REMINDER_LAST_SHOWN: 'ratingReminderLastShown',
|
||||
SETTING_RATING_REMINDER_DISABLED: 'ratingReminderDisabled',
|
||||
SETTING_BACKUP_DISMISSED: 'backupDismissed',
|
||||
SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
|
||||
SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
|
||||
|
||||
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS",
|
||||
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
|
||||
ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
|
||||
ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
|
||||
|
||||
ACTION_PUSH_DRAWER_STACK: "PUSH_DRAWER_STACK",
|
||||
ACTION_POP_DRAWER_STACK: "POP_DRAWER_STACK",
|
||||
ACTION_SET_PLAYER_VISIBLE: "SET_PLAYER_VISIBLE",
|
||||
ACTION_PUSH_DRAWER_STACK: 'PUSH_DRAWER_STACK',
|
||||
ACTION_POP_DRAWER_STACK: 'POP_DRAWER_STACK',
|
||||
ACTION_SET_PLAYER_VISIBLE: 'SET_PLAYER_VISIBLE',
|
||||
|
||||
ACTION_REACT_NAVIGATION_RESET: "Navigation/RESET",
|
||||
ACTION_REACT_NAVIGATION_NAVIGATE: "Navigation/NAVIGATE",
|
||||
ACTION_REACT_NAVIGATION_REPLACE: "Navigation/REPLACE",
|
||||
ACTION_REACT_NAVIGATION_RESET: 'Navigation/RESET',
|
||||
ACTION_REACT_NAVIGATION_NAVIGATE: 'Navigation/NAVIGATE',
|
||||
ACTION_REACT_NAVIGATION_REPLACE: 'Navigation/REPLACE',
|
||||
|
||||
PAGE_REWARDS: "rewards",
|
||||
PAGE_SETTINGS: "settings",
|
||||
PAGE_TRENDING: "trending",
|
||||
PAGE_WALLET: "wallet",
|
||||
PAGE_REWARDS: 'rewards',
|
||||
PAGE_SETTINGS: 'settings',
|
||||
PAGE_TRENDING: 'trending',
|
||||
PAGE_WALLET: 'wallet',
|
||||
|
||||
DRAWER_ROUTE_DISCOVER: "Discover",
|
||||
DRAWER_ROUTE_TRENDING: "Trending",
|
||||
DRAWER_ROUTE_SUBSCRIPTIONS: "Subscriptions",
|
||||
DRAWER_ROUTE_MY_LBRY: "Downloads",
|
||||
DRAWER_ROUTE_PUBLISH: "Publish",
|
||||
DRAWER_ROUTE_REWARDS: "Rewards",
|
||||
DRAWER_ROUTE_WALLET: "Wallet",
|
||||
DRAWER_ROUTE_SETTINGS: "Settings",
|
||||
DRAWER_ROUTE_ABOUT: "About",
|
||||
DRAWER_ROUTE_SEARCH: "Search",
|
||||
DRAWER_ROUTE_TRANSACTION_HISTORY: "TransactionHistory",
|
||||
DRAWER_ROUTE_DISCOVER: 'Discover',
|
||||
DRAWER_ROUTE_TRENDING: 'Trending',
|
||||
DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
|
||||
DRAWER_ROUTE_MY_LBRY: 'Downloads',
|
||||
DRAWER_ROUTE_REWARDS: 'Rewards',
|
||||
DRAWER_ROUTE_WALLET: 'Wallet',
|
||||
DRAWER_ROUTE_SETTINGS: 'Settings',
|
||||
DRAWER_ROUTE_ABOUT: 'About',
|
||||
DRAWER_ROUTE_SEARCH: 'Search',
|
||||
DRAWER_ROUTE_TRANSACTION_HISTORY: 'TransactionHistory',
|
||||
|
||||
FULL_ROUTE_NAME_DISCOVER: "DiscoverStack",
|
||||
FULL_ROUTE_NAME_TRENDING: "TrendingStack",
|
||||
FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: "MySubscriptionsStack",
|
||||
FULL_ROUTE_NAME_WALLET: "WalletStack",
|
||||
FULL_ROUTE_NAME_MY_LBRY: "MyLBRYStack",
|
||||
FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
|
||||
FULL_ROUTE_NAME_TRENDING: 'TrendingStack',
|
||||
FULL_ROUTE_NAME_MY_SUBSCRIPTIONS: 'MySubscriptionsStack',
|
||||
FULL_ROUTE_NAME_WALLET: 'WalletStack',
|
||||
FULL_ROUTE_NAME_MY_LBRY: 'MyLBRYStack',
|
||||
|
||||
ROUTE_FILE: "File",
|
||||
ROUTE_FILE: 'File',
|
||||
|
||||
SUBSCRIPTIONS_VIEW_ALL: "view_all",
|
||||
SUBSCRIPTIONS_VIEW_LATEST_FIRST: "view_latest_first",
|
||||
SUBSCRIPTIONS_VIEW_ALL: 'view_all',
|
||||
SUBSCRIPTIONS_VIEW_LATEST_FIRST: 'view_latest_first',
|
||||
|
||||
PLAY_STORE_URL: "https://play.google.com/store/apps/details?id=io.lbry.browser",
|
||||
PLAY_STORE_URL: 'https://play.google.com/store/apps/details?id=io.lbry.browser',
|
||||
RATING_REMINDER_INTERVAL: 604800, // 7 days (7 * 24 * 3600s)
|
||||
};
|
||||
|
||||
|
@ -83,5 +82,5 @@ export const DrawerRoutes = [
|
|||
Constants.DRAWER_ROUTE_SETTINGS,
|
||||
Constants.DRAWER_ROUTE_ABOUT,
|
||||
Constants.DRAWER_ROUTE_SEARCH,
|
||||
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY
|
||||
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY,
|
||||
];
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import React from 'react';
|
||||
import { setJSExceptionHandler } from 'react-native-exception-handler';
|
||||
import { Provider, connect } from 'react-redux';
|
||||
import {
|
||||
AppRegistry,
|
||||
Text,
|
||||
View,
|
||||
NativeModules
|
||||
} from 'react-native';
|
||||
import { AppRegistry, Text, View, NativeModules } from 'react-native';
|
||||
import {
|
||||
Lbry,
|
||||
claimsReducer,
|
||||
|
@ -15,7 +10,7 @@ import {
|
|||
fileInfoReducer,
|
||||
notificationsReducer,
|
||||
searchReducer,
|
||||
walletReducer
|
||||
walletReducer,
|
||||
} from 'lbry-redux';
|
||||
import {
|
||||
authReducer,
|
||||
|
@ -25,10 +20,14 @@ import {
|
|||
rewardsReducer,
|
||||
subscriptionsReducer,
|
||||
syncReducer,
|
||||
userReducer
|
||||
userReducer,
|
||||
} from 'lbryinc';
|
||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||
import AppWithNavigationState, { AppNavigator, navigatorReducer, reactNavigationMiddleware } from 'component/AppNavigator';
|
||||
import AppWithNavigationState, {
|
||||
AppNavigator,
|
||||
navigatorReducer,
|
||||
reactNavigationMiddleware,
|
||||
} from 'component/AppNavigator';
|
||||
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import FilesystemStorage from 'redux-persist-filesystem-storage';
|
||||
|
@ -39,10 +38,9 @@ import drawerReducer from 'redux/reducers/drawer';
|
|||
import settingsReducer from 'redux/reducers/settings';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
It did. But it looks like the precommit script doesn't fire up if I run the commit from the top-level folder. I'll look into fixing this. It did. But it looks like the precommit script doesn't fire up if I run the commit from the top-level folder. I'll look into fixing this.
|
||||
|
||||
const globalExceptionHandler = (error, isFatal) => {
|
||||
if (error && NativeModules.Firebase) {
|
||||
NativeModules.Firebase.logException(isFatal, error.message ? error.message : "No message", JSON.stringify(error));
|
||||
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
setJSExceptionHandler(globalExceptionHandler, true);
|
||||
|
@ -97,7 +95,7 @@ const reducers = combineReducers({
|
|||
subscriptions: subscriptionsReducer,
|
||||
sync: syncReducer,
|
||||
user: userReducer,
|
||||
wallet: walletReducer
|
||||
wallet: walletReducer,
|
||||
});
|
||||
|
||||
const bulkThunk = createBulkThunkMiddleware();
|
||||
|
@ -109,10 +107,7 @@ const composeEnhancers = compose;
|
|||
const store = createStore(
|
||||
enableBatching(reducers),
|
||||
{}, // initial state,
|
||||
composeEnhancers(
|
||||
autoRehydrate(),
|
||||
applyMiddleware(...middleware)
|
||||
)
|
||||
composeEnhancers(autoRehydrate(), applyMiddleware(...middleware))
|
||||
);
|
||||
window.store = store;
|
||||
|
||||
|
@ -130,7 +125,7 @@ const persistOptions = {
|
|||
// read the data
|
||||
transforms: [authFilter, saveClaimsFilter, subscriptionsFilter, settingsFilter, walletFilter, compressor],
|
||||
debounce: 10000,
|
||||
storage: FilesystemStorage
|
||||
storage: FilesystemStorage,
|
||||
};
|
||||
|
||||
persistStore(store, persistOptions, err => {
|
||||
|
@ -140,7 +135,7 @@ persistStore(store, persistOptions, err => {
|
|||
});
|
||||
|
||||
// TODO: Find i18n module that is compatible with react-native
|
||||
global.__ = (str) => str;
|
||||
global.__ = str => str;
|
||||
|
||||
class LBRYApp extends React.Component {
|
||||
render() {
|
||||
|
|
|
@ -21,4 +21,7 @@ const perform = dispatch => ({
|
|||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(AboutPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(AboutPage);
|
||||
|
|
|
@ -11,7 +11,7 @@ class AboutPage extends React.PureComponent {
|
|||
state = {
|
||||
appVersion: null,
|
||||
lbryId: null,
|
||||
versionInfo: null
|
||||
versionInfo: null,
|
||||
};
|
||||
|
||||
didFocusListener;
|
||||
|
@ -46,7 +46,7 @@ class AboutPage extends React.PureComponent {
|
|||
|
||||
if (NativeModules.VersionInfo) {
|
||||
NativeModules.VersionInfo.getAppVersion().then(version => {
|
||||
this.setState({appVersion: version});
|
||||
this.setState({ appVersion: version });
|
||||
});
|
||||
}
|
||||
Lbry.version().then(info => {
|
||||
|
@ -61,7 +61,7 @@ class AboutPage extends React.PureComponent {
|
|||
});
|
||||
|
||||
if (!this.props.accessToken) this.props.fetchAccessToken();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { accessToken, drawerStack, navigation, notify, popDrawerStack, userEmail } = this.props;
|
||||
|
@ -70,15 +70,14 @@ class AboutPage extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<View style={aboutStyle.container}>
|
||||
<PageHeader title={"About LBRY"}
|
||||
onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
||||
<PageHeader title={'About LBRY'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
||||
<ScrollView style={aboutStyle.scrollContainer}>
|
||||
<Text style={aboutStyle.title}>Content Freedom</Text>
|
||||
<Text style={aboutStyle.paragraph}>
|
||||
LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer
|
||||
content distribution platform for creators to upload and share content, and earn LBRY credits
|
||||
for their effort. Users will be able to find a wide selection of videos, music, ebooks and other
|
||||
digital content they are interested in.
|
||||
LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content
|
||||
distribution platform for creators to upload and share content, and earn LBRY credits for their effort.
|
||||
Users will be able to find a wide selection of videos, music, ebooks and other digital content they are
|
||||
interested in.
|
||||
</Text>
|
||||
<View style={aboutStyle.links}>
|
||||
<Link style={aboutStyle.link} href="https://lbry.com/faq/what-is-lbry" text="What is LBRY?" />
|
||||
|
@ -87,7 +86,8 @@ class AboutPage extends React.PureComponent {
|
|||
</View>
|
||||
<Text style={aboutStyle.socialTitle}>Get Social</Text>
|
||||
<Text style={aboutStyle.paragraph}>
|
||||
You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit.
|
||||
You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or
|
||||
Reddit.
|
||||
</Text>
|
||||
<View style={aboutStyle.links}>
|
||||
<Link style={aboutStyle.link} href="https://discordapp.com/invite/Z3bERWA" text="Discord" />
|
||||
|
@ -98,54 +98,88 @@ class AboutPage extends React.PureComponent {
|
|||
<Link style={aboutStyle.link} href="https://t.me/lbryofficial" text="Telegram" />
|
||||
</View>
|
||||
<Text style={aboutStyle.releaseInfoTitle}>App info</Text>
|
||||
{userEmail && userEmail.trim().length > 0 &&
|
||||
<View style={aboutStyle.verticalRow}>
|
||||
<View style={aboutStyle.innerRow}>
|
||||
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Connected Email</Text></View>
|
||||
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{userEmail}</Text></View>
|
||||
{userEmail && userEmail.trim().length > 0 && (
|
||||
<View style={aboutStyle.verticalRow}>
|
||||
<View style={aboutStyle.innerRow}>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text style={aboutStyle.text}>Connected Email</Text>
|
||||
</View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text selectable={true} style={aboutStyle.valueText}>
|
||||
{userEmail}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Link
|
||||
style={aboutStyle.listLink}
|
||||
href={`http://lbry.com/list/edit/${accessToken}`}
|
||||
text="Update mailing preferences"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Link
|
||||
style={aboutStyle.listLink}
|
||||
href={`http://lbry.com/list/edit/${accessToken}`}
|
||||
text="Update mailing preferences" />
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
|
||||
<View style={aboutStyle.row}>
|
||||
<View style={aboutStyle.col}><Text style={aboutStyle.text}>App version</Text></View>
|
||||
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{this.state.appVersion}</Text></View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text style={aboutStyle.text}>App version</Text>
|
||||
</View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text selectable={true} style={aboutStyle.valueText}>
|
||||
{this.state.appVersion}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={aboutStyle.row}>
|
||||
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Daemon (lbrynet)</Text></View>
|
||||
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{ver ? ver.lbrynet_version : loading }</Text></View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text style={aboutStyle.text}>Daemon (lbrynet)</Text>
|
||||
</View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text selectable={true} style={aboutStyle.valueText}>
|
||||
{ver ? ver.lbrynet_version : loading}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={aboutStyle.row}>
|
||||
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Platform</Text></View>
|
||||
<View style={aboutStyle.col}><Text selectable={true} style={aboutStyle.valueText}>{ver ? ver.platform : loading }</Text></View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text style={aboutStyle.text}>Platform</Text>
|
||||
</View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text selectable={true} style={aboutStyle.valueText}>
|
||||
{ver ? ver.platform : loading}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={aboutStyle.row}>
|
||||
<View style={aboutStyle.col}>
|
||||
<Text style={aboutStyle.text}>Installation ID</Text>
|
||||
<Text selectable={true} style={aboutStyle.lineValueText}>{this.state.lbryId ? this.state.lbryId : loading}</Text>
|
||||
<Text selectable={true} style={aboutStyle.lineValueText}>
|
||||
{this.state.lbryId ? this.state.lbryId : loading}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={aboutStyle.row}>
|
||||
<View style={aboutStyle.col}><Text style={aboutStyle.text}>Logs</Text></View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Link style={aboutStyle.listLink} text="Send log" onPress={() => {
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.shareLogFile((error) => {
|
||||
if (error) {
|
||||
notify(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}} />
|
||||
<Text style={aboutStyle.text}>Logs</Text>
|
||||
</View>
|
||||
<View style={aboutStyle.col}>
|
||||
<Link
|
||||
style={aboutStyle.listLink}
|
||||
text="Send log"
|
||||
onPress={() => {
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.shareLogFile(error => {
|
||||
if (error) {
|
||||
notify(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
makeSelectClaimForUri,
|
||||
makeSelectClaimsInChannelForCurrentPageState,
|
||||
makeSelectFetchingChannelClaims,
|
||||
makeSelectTotalPagesForChannel
|
||||
makeSelectTotalPagesForChannel,
|
||||
} from 'lbry-redux';
|
||||
import { doPopDrawerStack } from 'redux/actions/drawer';
|
||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||
|
@ -20,7 +20,10 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
||||
popDrawerStack: () => dispatch(doPopDrawerStack())
|
||||
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ChannelPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ChannelPage);
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
Image,
|
||||
ScrollView,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Dimensions, Image, ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { TabView, SceneMap } from 'react-native-tab-view';
|
||||
import { navigateBack } from 'utils/helper';
|
||||
import Colors from 'styles/colors';
|
||||
|
@ -25,7 +17,7 @@ class ChannelPage extends React.PureComponent {
|
|||
state = {
|
||||
page: 1,
|
||||
showPageButtons: false,
|
||||
activeTab: Constants.CONTENT_TAB
|
||||
activeTab: Constants.CONTENT_TAB,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -43,7 +35,7 @@ class ChannelPage extends React.PureComponent {
|
|||
fetchClaims(uri, this.state.page);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleNextPage = () => {
|
||||
const { uri, fetchClaims, totalPages } = this.props;
|
||||
|
@ -52,7 +44,7 @@ class ChannelPage extends React.PureComponent {
|
|||
fetchClaims(uri, this.state.page);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const { fetching, claimsInChannel, totalPages, navigation } = this.props;
|
||||
|
@ -68,13 +60,15 @@ class ChannelPage extends React.PureComponent {
|
|||
} else {
|
||||
contentList =
|
||||
claimsInChannel && claimsInChannel.length ? (
|
||||
<FileList sortByHeight
|
||||
hideFilter
|
||||
fileInfos={claimsInChannel}
|
||||
navigation={navigation}
|
||||
style={channelPageStyle.fileList}
|
||||
contentContainerStyle={channelPageStyle.fileListContent}
|
||||
onEndReached={() => this.setState({ showPageButtons: true })} />
|
||||
<FileList
|
||||
sortByHeight
|
||||
hideFilter
|
||||
fileInfos={claimsInChannel}
|
||||
navigation={navigation}
|
||||
style={channelPageStyle.fileList}
|
||||
contentContainerStyle={channelPageStyle.fileListContent}
|
||||
onEndReached={() => this.setState({ showPageButtons: true })}
|
||||
/>
|
||||
) : (
|
||||
<View style={channelPageStyle.busyContainer}>
|
||||
<Text style={channelPageStyle.infoText}>No content found.</Text>
|
||||
|
@ -87,17 +81,23 @@ class ChannelPage extends React.PureComponent {
|
|||
pageButtons = (
|
||||
<View style={channelPageStyle.pageButtons}>
|
||||
<View>
|
||||
{(this.state.page > 1) && <Button
|
||||
style={channelPageStyle.button}
|
||||
text={"Previous"}
|
||||
disabled={!!fetching}
|
||||
onPress={this.handlePreviousPage} />}
|
||||
{this.state.page > 1 && (
|
||||
<Button
|
||||
style={channelPageStyle.button}
|
||||
text={'Previous'}
|
||||
disabled={!!fetching}
|
||||
onPress={this.handlePreviousPage}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{(this.state.page < totalPages) && <Button
|
||||
style={[channelPageStyle.button, channelPageStyle.nextButton]}
|
||||
text={"Next"}
|
||||
disabled={!!fetching}
|
||||
onPress={this.handleNextPage} />}
|
||||
{this.state.page < totalPages && (
|
||||
<Button
|
||||
style={[channelPageStyle.button, channelPageStyle.nextButton]}
|
||||
text={'Next'}
|
||||
disabled={!!fetching}
|
||||
onPress={this.handleNextPage}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class ChannelPage extends React.PureComponent {
|
|||
{pageButtons}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderAbout = () => {
|
||||
const { claim } = this.props;
|
||||
|
@ -126,45 +126,41 @@ class ChannelPage extends React.PureComponent {
|
|||
const { cover, description, thumbnail, email, website_url, title } = claim.value;
|
||||
return (
|
||||
<View style={channelPageStyle.aboutTab}>
|
||||
{(!website_url && !email && !description) &&
|
||||
{!website_url && !email && !description && (
|
||||
<View style={channelPageStyle.busyContainer}>
|
||||
<Text style={channelPageStyle.infoText}>Nothing here yet. Please check back later.</Text>
|
||||
</View>}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(website_url || email || description) &&
|
||||
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
|
||||
{(website_url && website_url.trim().length > 0) &&
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutTitle}>Website</Text>
|
||||
<Link style={channelPageStyle.aboutText} text={website_url} href={website_url} />
|
||||
</View>}
|
||||
{(website_url || email || description) && (
|
||||
<ScrollView style={channelPageStyle.aboutScroll} contentContainerStyle={channelPageStyle.aboutScrollContent}>
|
||||
{website_url && website_url.trim().length > 0 && (
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutTitle}>Website</Text>
|
||||
<Link style={channelPageStyle.aboutText} text={website_url} href={website_url} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(email && email.trim().length > 0) &&
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutTitle}>Email</Text>
|
||||
<Link style={channelPageStyle.aboutText} text={email} href={`mailto:${email}`} />
|
||||
</View>}
|
||||
{email && email.trim().length > 0 && (
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutTitle}>Email</Text>
|
||||
<Link style={channelPageStyle.aboutText} text={email} href={`mailto:${email}`} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(description && description.trim().length > 0) &&
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutText}>{description}</Text>
|
||||
</View>}
|
||||
</ScrollView>}
|
||||
{description && description.trim().length > 0 && (
|
||||
<View style={channelPageStyle.aboutItem}>
|
||||
<Text style={channelPageStyle.aboutText}>{description}</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
fetching,
|
||||
claimsInChannel,
|
||||
claim,
|
||||
navigation,
|
||||
totalPages,
|
||||
uri,
|
||||
drawerStack,
|
||||
popDrawerStack
|
||||
} = this.props;
|
||||
const { fetching, claimsInChannel, claim, navigation, totalPages, uri, drawerStack, popDrawerStack } = this.props;
|
||||
const { name, permanent_url: permanentUrl } = claim;
|
||||
|
||||
let thumbnailUrl, coverUrl, title;
|
||||
|
@ -187,28 +183,44 @@ class ChannelPage extends React.PureComponent {
|
|||
<Image
|
||||
style={channelPageStyle.coverImage}
|
||||
resizeMode={'cover'}
|
||||
source={(coverUrl && coverUrl.trim().length > 0) ? { uri: coverUrl } : require('../../assets/default_channel_cover.png')} />
|
||||
source={
|
||||
coverUrl && coverUrl.trim().length > 0
|
||||
? { uri: coverUrl }
|
||||
: require('../../assets/default_channel_cover.png')
|
||||
}
|
||||
/>
|
||||
|
||||
<View style={channelPageStyle.channelHeader}>
|
||||
<Text style={channelPageStyle.channelName}>{(title && title.trim().length > 0) ? title : name}</Text>
|
||||
<Text style={channelPageStyle.channelName}>{title && title.trim().length > 0 ? title : name}</Text>
|
||||
</View>
|
||||
|
||||
<View style={channelPageStyle.avatarImageContainer}>
|
||||
<Image
|
||||
style={channelPageStyle.avatarImage}
|
||||
resizeMode={'cover'}
|
||||
source={(thumbnailUrl && thumbnailUrl.trim().length > 0) ? { uri: thumbnailUrl } : require('../../assets/default_avatar.jpg')} />
|
||||
source={
|
||||
thumbnailUrl && thumbnailUrl.trim().length > 0
|
||||
? { uri: thumbnailUrl }
|
||||
: require('../../assets/default_avatar.jpg')
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<SubscribeButton style={channelPageStyle.subscribeButton} uri={uri} name={name} />
|
||||
</View>
|
||||
|
||||
<View style={channelPageStyle.tabBar}>
|
||||
<TouchableOpacity style={channelPageStyle.tab} onPress={() => this.setState({ activeTab: Constants.CONTENT_TAB })}>
|
||||
<TouchableOpacity
|
||||
style={channelPageStyle.tab}
|
||||
onPress={() => this.setState({ activeTab: Constants.CONTENT_TAB })}
|
||||
>
|
||||
<Text style={channelPageStyle.tabTitle}>CONTENT</Text>
|
||||
{Constants.CONTENT_TAB === this.state.activeTab && <View style={channelPageStyle.activeTabHint} />}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={channelPageStyle.tab} onPress={() => this.setState({ activeTab: Constants.ABOUT_TAB })}>
|
||||
<TouchableOpacity
|
||||
style={channelPageStyle.tab}
|
||||
onPress={() => this.setState({ activeTab: Constants.ABOUT_TAB })}
|
||||
>
|
||||
<Text style={channelPageStyle.tabTitle}>ABOUT</Text>
|
||||
{Constants.ABOUT_TAB === this.state.activeTab && <View style={channelPageStyle.activeTabHint} />}
|
||||
</TouchableOpacity>
|
||||
|
@ -218,7 +230,7 @@ class ChannelPage extends React.PureComponent {
|
|||
{Constants.ABOUT_TAB === this.state.activeTab && this.renderAbout()}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doFileList,
|
||||
selectBalance,
|
||||
selectFileInfosDownloaded,
|
||||
} from 'lbry-redux';
|
||||
import { doFileList, selectBalance, selectFileInfosDownloaded } from 'lbry-redux';
|
||||
import {
|
||||
doFetchFeaturedUris,
|
||||
doFetchRewardedContent,
|
||||
|
@ -38,7 +34,10 @@ const perform = dispatch => ({
|
|||
fetchSubscriptions: () => dispatch(doFetchMySubscriptions()),
|
||||
fileList: () => dispatch(doFileList()),
|
||||
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value))
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(DiscoverPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(DiscoverPage);
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import React from 'react';
|
||||
import NavigationActions from 'react-navigation';
|
||||
import {
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
NativeModules,
|
||||
SectionList,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { Alert, ActivityIndicator, Linking, NativeModules, SectionList, Text, View } from 'react-native';
|
||||
import { Lbry, normalizeURI, parseURI } from 'lbry-redux';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import moment from 'moment';
|
||||
|
@ -33,22 +25,18 @@ class DiscoverPage extends React.PureComponent {
|
|||
const delta = now - start;
|
||||
AsyncStorage.getItem('firstLaunchSuspended').then(suspended => {
|
||||
AsyncStorage.removeItem('firstLaunchSuspended');
|
||||
const appSuspended = (suspended === 'true');
|
||||
const appSuspended = suspended === 'true';
|
||||
if (NativeModules.Firebase) {
|
||||
NativeModules.Firebase.track('first_run_time', {
|
||||
'total_seconds': delta, 'app_suspended': appSuspended
|
||||
total_seconds: delta,
|
||||
app_suspended: appSuspended,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
fetchFeaturedUris,
|
||||
fetchRewardedContent,
|
||||
fetchSubscriptions,
|
||||
fileList
|
||||
} = this.props;
|
||||
const { fetchFeaturedUris, fetchRewardedContent, fetchSubscriptions, fileList } = this.props;
|
||||
|
||||
fetchFeaturedUris();
|
||||
fetchRewardedContent();
|
||||
|
@ -73,14 +61,15 @@ class DiscoverPage extends React.PureComponent {
|
|||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { unreadSubscriptions, enabledChannelNotifications } = this.props;
|
||||
|
||||
const utility = NativeModules.UtilityModule;
|
||||
if (utility) {
|
||||
const hasUnread = prevProps.unreadSubscriptions &&
|
||||
const hasUnread =
|
||||
prevProps.unreadSubscriptions &&
|
||||
prevProps.unreadSubscriptions.length !== unreadSubscriptions.length &&
|
||||
unreadSubscriptions.length > 0;
|
||||
|
||||
|
@ -98,10 +87,17 @@ class DiscoverPage extends React.PureComponent {
|
|||
const source = sub.value.stream.source;
|
||||
const metadata = sub.value.stream.metadata;
|
||||
if (source) {
|
||||
isPlayable = source.contentType && ['audio', 'video'].indexOf(source.contentType.substring(0, 5)) > -1;
|
||||
isPlayable =
|
||||
source.contentType && ['audio', 'video'].indexOf(source.contentType.substring(0, 5)) > -1;
|
||||
}
|
||||
if (metadata) {
|
||||
utility.showNotificationForContent(uri, metadata.title, channelName, metadata.thumbnail, isPlayable);
|
||||
utility.showNotificationForContent(
|
||||
uri,
|
||||
metadata.title,
|
||||
channelName,
|
||||
metadata.thumbnail,
|
||||
isPlayable
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -122,17 +118,23 @@ class DiscoverPage extends React.PureComponent {
|
|||
const lastShownTime = parseInt(lastShownParts[0], 10);
|
||||
const lastShownCount = parseInt(lastShownParts[1], 10);
|
||||
if (!isNaN(lastShownTime) && !isNaN(lastShownCount)) {
|
||||
if (now > (lastShownTime + (Constants.RATING_REMINDER_INTERVAL * lastShownCount))) {
|
||||
if (now > lastShownTime + Constants.RATING_REMINDER_INTERVAL * lastShownCount) {
|
||||
Alert.alert(
|
||||
'Enjoying LBRY?',
|
||||
'Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.',
|
||||
[
|
||||
{ text: 'Never ask again', onPress: () => setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true')},
|
||||
{ text: 'Maybe later', onPress: () => this.updateRatingReminderShown(lastShownCount)},
|
||||
{ text: 'Rate app', onPress: () => {
|
||||
setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true');
|
||||
Linking.openURL(Constants.PLAY_STORE_URL);
|
||||
}}
|
||||
{
|
||||
text: 'Never ask again',
|
||||
onPress: () => setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true'),
|
||||
},
|
||||
{ text: 'Maybe later', onPress: () => this.updateRatingReminderShown(lastShownCount) },
|
||||
{
|
||||
text: 'Rate app',
|
||||
onPress: () => {
|
||||
setClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED, 'true');
|
||||
Linking.openURL(Constants.PLAY_STORE_URL);
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
|
@ -144,13 +146,13 @@ class DiscoverPage extends React.PureComponent {
|
|||
// first time, so set a value for the next interval multiplier
|
||||
this.updateRatingReminderShown(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateRatingReminderShown = (lastShownCount) => {
|
||||
updateRatingReminderShown = lastShownCount => {
|
||||
const { setClientSetting } = this.props;
|
||||
const settingString = (moment().unix() + '|' + (lastShownCount + 1));
|
||||
const settingString = moment().unix() + '|' + (lastShownCount + 1);
|
||||
setClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN, settingString);
|
||||
}
|
||||
};
|
||||
|
||||
trimClaimIdFromCategory(category) {
|
||||
return category.split('#')[0];
|
||||
|
@ -170,27 +172,24 @@ class DiscoverPage extends React.PureComponent {
|
|||
<Text style={discoverStyle.title}>Fetching content...</Text>
|
||||
</View>
|
||||
)}
|
||||
{(!!hasContent) &&
|
||||
(<SectionList
|
||||
{!!hasContent && (
|
||||
<SectionList
|
||||
style={discoverStyle.scrollContainer}
|
||||
contentContainerStyle={discoverStyle.scrollPadding}
|
||||
initialNumToRender={4}
|
||||
maxToRenderPerBatch={4}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={ ({item, index, section}) => (
|
||||
<CategoryList
|
||||
key={item}
|
||||
category={item}
|
||||
categoryMap={featuredUris}
|
||||
navigation={navigation} />
|
||||
renderItem={({ item, index, section }) => (
|
||||
<CategoryList key={item} category={item} categoryMap={featuredUris} navigation={navigation} />
|
||||
)}
|
||||
renderSectionHeader={
|
||||
({section: {title}}) => (<Text style={discoverStyle.categoryName}>{title}</Text>)
|
||||
}
|
||||
sections={Object.keys(featuredUris).map(category => ({ title: this.trimClaimIdFromCategory(category), data: [category] }))}
|
||||
renderSectionHeader={({ section: { title } }) => <Text style={discoverStyle.categoryName}>{title}</Text>}
|
||||
sections={Object.keys(featuredUris).map(category => ({
|
||||
title: this.trimClaimIdFromCategory(category),
|
||||
data: [category],
|
||||
}))}
|
||||
keyExtractor={(item, index) => item}
|
||||
/>)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FloatingWalletBalance navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { selectCurrentRoute } from 'redux/selectors/drawer';
|
|||
import Constants from 'constants';
|
||||
import DownloadsPage from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
const select = state => ({
|
||||
claims: selectMyClaimsWithoutChannels(state),
|
||||
currentRoute: selectCurrentRoute(state),
|
||||
fileInfos: selectFileInfosDownloaded(state),
|
||||
|
@ -20,7 +20,10 @@ const select = (state) => ({
|
|||
const perform = dispatch => ({
|
||||
fileList: () => dispatch(doFileList()),
|
||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_MY_LBRY)),
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(DownloadsPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(DownloadsPage);
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry, buildURI } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
FlatList,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Button, FlatList, Text, TextInput, View, ScrollView } from 'react-native';
|
||||
import { navigateToUri, uriFromFileInfo } from 'utils/helper';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
|
@ -22,7 +14,7 @@ import fileListStyle from 'styles/fileList';
|
|||
|
||||
class DownloadsPage extends React.PureComponent {
|
||||
static navigationOptions = {
|
||||
title: 'Downloads'
|
||||
title: 'Downloads',
|
||||
};
|
||||
|
||||
didFocusListener;
|
||||
|
@ -43,7 +35,7 @@ class DownloadsPage extends React.PureComponent {
|
|||
pushDrawerStack();
|
||||
setPlayerVisible();
|
||||
fileList();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -64,32 +56,37 @@ class DownloadsPage extends React.PureComponent {
|
|||
return (
|
||||
<View style={downloadsStyle.container}>
|
||||
<UriBar navigation={navigation} />
|
||||
{!fetching && !hasDownloads &&
|
||||
{!fetching && !hasDownloads && (
|
||||
<View style={downloadsStyle.busyContainer}>
|
||||
<Text style={downloadsStyle.noDownloadsText}>You have not watched or downloaded any content from LBRY yet.</Text>
|
||||
</View>}
|
||||
{fetching && !hasDownloads &&
|
||||
<Text style={downloadsStyle.noDownloadsText}>
|
||||
You have not watched or downloaded any content from LBRY yet.
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{fetching && !hasDownloads && (
|
||||
<View style={downloadsStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} style={downloadsStyle.loading} />
|
||||
</View>}
|
||||
{hasDownloads &&
|
||||
</View>
|
||||
)}
|
||||
{hasDownloads && (
|
||||
<View style={downloadsStyle.subContainer}>
|
||||
<StorageStatsCard fileInfos={fileInfos} />
|
||||
<FlatList
|
||||
style={downloadsStyle.scrollContainer}
|
||||
contentContainerStyle={downloadsStyle.scrollPadding}
|
||||
renderItem={ ({item}) => (
|
||||
<FileListItem
|
||||
style={fileListStyle.item}
|
||||
uri={uriFromFileInfo(item)}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, uriFromFileInfo(item), { autoplay: true })} />
|
||||
)
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<FileListItem
|
||||
style={fileListStyle.item}
|
||||
uri={uriFromFileInfo(item)}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, uriFromFileInfo(item), { autoplay: true })}
|
||||
/>
|
||||
)}
|
||||
data={fileInfos}
|
||||
keyExtractor={(item, index) => item.outpoint}
|
||||
/>
|
||||
</View>}
|
||||
</View>
|
||||
)}
|
||||
<FloatingWalletBalance navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -26,14 +26,14 @@ import {
|
|||
doFetchCostInfoForUri,
|
||||
makeSelectCostInfoForUri,
|
||||
selectRewardContentClaimIds,
|
||||
selectBlackListedOutpoints
|
||||
selectBlackListedOutpoints,
|
||||
} from 'lbryinc';
|
||||
import {
|
||||
doStartDownload,
|
||||
doUpdateDownload,
|
||||
doCompleteDownload,
|
||||
doDeleteFile,
|
||||
doStopDownloadingFile
|
||||
doStopDownloadingFile,
|
||||
} from 'redux/actions/file';
|
||||
import { doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||
|
@ -77,7 +77,8 @@ const perform = dispatch => ({
|
|||
purchaseUri: (uri, costInfo, saveFile) => dispatch(doPurchaseUri(uri, costInfo, saveFile)),
|
||||
deletePurchasedUri: uri => dispatch(doDeletePurchasedUri(uri)),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
sendTip: (amount, claimId, uri, successCallback, errorCallback) => dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
|
||||
sendTip: (amount, claimId, uri, successCallback, errorCallback) =>
|
||||
dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)),
|
||||
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
||||
startDownload: (uri, outpoint, fileInfo) => dispatch(doStartDownload(uri, outpoint, fileInfo)),
|
||||
|
@ -85,4 +86,7 @@ const perform = dispatch => ({
|
|||
completeDownload: (uri, outpoint, fileInfo) => dispatch(doCompleteDownload(uri, outpoint, fileInfo)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FilePage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FilePage);
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
WebView
|
||||
WebView,
|
||||
} from 'react-native';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
import { navigateBack, navigateToUri } from 'utils/helper';
|
||||
|
@ -43,7 +43,7 @@ import uriBarStyle from 'styles/uriBar';
|
|||
|
||||
class FilePage extends React.PureComponent {
|
||||
static navigationOptions = {
|
||||
title: ''
|
||||
title: '',
|
||||
};
|
||||
|
||||
tipAmountInput = null;
|
||||
|
@ -79,7 +79,7 @@ class FilePage extends React.PureComponent {
|
|||
uri: null,
|
||||
uriVars: null,
|
||||
stopDownloadConfirmed: false,
|
||||
streamingMode: false
|
||||
streamingMode: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ class FilePage extends React.PureComponent {
|
|||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.keepAwakeOn();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -126,7 +126,7 @@ class FilePage extends React.PureComponent {
|
|||
purchasedUris: prevPurchasedUris,
|
||||
navigation,
|
||||
contentType,
|
||||
notify
|
||||
notify,
|
||||
} = this.props;
|
||||
const { uri } = navigation.state.params;
|
||||
const {
|
||||
|
@ -135,7 +135,7 @@ class FilePage extends React.PureComponent {
|
|||
fileInfo,
|
||||
purchasedUris,
|
||||
purchaseUriErrorMessage,
|
||||
streamingUrl
|
||||
streamingUrl,
|
||||
} = nextProps;
|
||||
|
||||
if (Constants.ROUTE_FILE === currentRoute && currentRoute !== prevRoute) {
|
||||
|
@ -151,7 +151,10 @@ class FilePage extends React.PureComponent {
|
|||
|
||||
const mediaType = Lbry.getMediaType(contentType);
|
||||
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
||||
if ((this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) && NativeModules.UtilityModule) {
|
||||
if (
|
||||
(this.state.fileGetStarted || prevPurchasedUris.length !== purchasedUris.length) &&
|
||||
NativeModules.UtilityModule
|
||||
) {
|
||||
if (purchasedUris.includes(uri)) {
|
||||
const { nout, txid } = claim;
|
||||
const outpoint = `${txid}:${nout}`;
|
||||
|
@ -187,7 +190,7 @@ class FilePage extends React.PureComponent {
|
|||
window.currentMediaInfo = {
|
||||
channel: claim ? claim.channel_name : null,
|
||||
title: metadata ? metadata.title : claim.name,
|
||||
uri: this.state.uri
|
||||
uri: this.state.uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +207,7 @@ class FilePage extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleFullscreenToggle = (mode) => {
|
||||
handleFullscreenToggle = mode => {
|
||||
this.setState({ fullscreenMode: mode });
|
||||
StatusBar.setHidden(mode);
|
||||
if (NativeModules.ScreenOrientation) {
|
||||
|
@ -224,7 +227,7 @@ class FilePage extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDeletePressed = () => {
|
||||
const { claim, deleteFile, deletePurchasedUri, fileInfo, navigation } = this.props;
|
||||
|
@ -234,24 +237,27 @@ class FilePage extends React.PureComponent {
|
|||
'Are you sure you want to remove this file from your device?',
|
||||
[
|
||||
{ text: 'No' },
|
||||
{ text: 'Yes', onPress: () => {
|
||||
const { uri } = navigation.state.params;
|
||||
deleteFile(`${claim.txid}:${claim.nout}`, true);
|
||||
deletePurchasedUri(uri);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.deleteDownload(uri);
|
||||
}
|
||||
this.setState({
|
||||
downloadPressed: false,
|
||||
fileViewLogged: false,
|
||||
mediaLoaded: false,
|
||||
stopDownloadConfirmed: false
|
||||
});
|
||||
}}
|
||||
{
|
||||
text: 'Yes',
|
||||
onPress: () => {
|
||||
const { uri } = navigation.state.params;
|
||||
deleteFile(`${claim.txid}:${claim.nout}`, true);
|
||||
deletePurchasedUri(uri);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.deleteDownload(uri);
|
||||
}
|
||||
this.setState({
|
||||
downloadPressed: false,
|
||||
fileViewLogged: false,
|
||||
mediaLoaded: false,
|
||||
stopDownloadConfirmed: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: true }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
onStopDownloadPressed = () => {
|
||||
const { deletePurchasedUri, fileInfo, navigation, notify, stopDownload } = this.props;
|
||||
|
@ -261,30 +267,33 @@ class FilePage extends React.PureComponent {
|
|||
'Are you sure you want to stop downloading this file?',
|
||||
[
|
||||
{ text: 'No' },
|
||||
{ text: 'Yes', onPress: () => {
|
||||
const { uri } = navigation.state.params;
|
||||
stopDownload(uri, fileInfo);
|
||||
deletePurchasedUri(uri);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.deleteDownload(uri);
|
||||
}
|
||||
this.setState({
|
||||
downloadPressed: false,
|
||||
fileViewLogged: false,
|
||||
mediaLoaded: false,
|
||||
stopDownloadConfirmed: true
|
||||
});
|
||||
{
|
||||
text: 'Yes',
|
||||
onPress: () => {
|
||||
const { uri } = navigation.state.params;
|
||||
stopDownload(uri, fileInfo);
|
||||
deletePurchasedUri(uri);
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.deleteDownload(uri);
|
||||
}
|
||||
this.setState({
|
||||
downloadPressed: false,
|
||||
fileViewLogged: false,
|
||||
mediaLoaded: false,
|
||||
stopDownloadConfirmed: true,
|
||||
});
|
||||
|
||||
// there can be a bit of lag between the user pressing Yes and the UI being updated
|
||||
// after the file_set_status and file_delete operations, so let the user know
|
||||
notify({
|
||||
message: 'The download will stop momentarily. You do not need to wait to discover something else.',
|
||||
});
|
||||
}}
|
||||
// there can be a bit of lag between the user pressing Yes and the UI being updated
|
||||
// after the file_set_status and file_delete operations, so let the user know
|
||||
notify({
|
||||
message: 'The download will stop momentarily. You do not need to wait to discover something else.',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: true }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
StatusBar.setHidden(false);
|
||||
|
@ -309,32 +318,32 @@ class FilePage extends React.PureComponent {
|
|||
DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted);
|
||||
}
|
||||
|
||||
handleDownloadStarted = (evt) => {
|
||||
handleDownloadStarted = evt => {
|
||||
const { startDownload } = this.props;
|
||||
const { uri, outpoint, fileInfo } = evt;
|
||||
startDownload(uri, outpoint, fileInfo);
|
||||
}
|
||||
};
|
||||
|
||||
handleDownloadUpdated = (evt) => {
|
||||
handleDownloadUpdated = evt => {
|
||||
const { updateDownload } = this.props;
|
||||
const { uri, outpoint, fileInfo, progress } = evt;
|
||||
updateDownload(uri, outpoint, fileInfo, progress);
|
||||
}
|
||||
};
|
||||
|
||||
handleDownloadCompleted = (evt) => {
|
||||
handleDownloadCompleted = evt => {
|
||||
const { completeDownload } = this.props;
|
||||
const { uri, outpoint, fileInfo } = evt;
|
||||
completeDownload(uri, outpoint, fileInfo);
|
||||
}
|
||||
};
|
||||
|
||||
localUriForFileInfo = (fileInfo) => {
|
||||
localUriForFileInfo = fileInfo => {
|
||||
if (!fileInfo) {
|
||||
return null;
|
||||
}
|
||||
return 'file:///' + fileInfo.download_path;
|
||||
}
|
||||
};
|
||||
|
||||
playerUriForFileInfo = (fileInfo) => {
|
||||
playerUriForFileInfo = fileInfo => {
|
||||
const { streamingUrl } = this.props;
|
||||
if (fileInfo && fileInfo.download_path) {
|
||||
return this.getEncodedDownloadPath(fileInfo);
|
||||
|
@ -347,9 +356,9 @@ class FilePage extends React.PureComponent {
|
|||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
getEncodedDownloadPath = (fileInfo) => {
|
||||
getEncodedDownloadPath = fileInfo => {
|
||||
if (this.state.encodedFilePath) {
|
||||
return this.state.encodedFilePath;
|
||||
}
|
||||
|
@ -358,36 +367,41 @@ class FilePage extends React.PureComponent {
|
|||
const encodedFileName = encodeURIComponent(fileName).replace(/!/g, '%21');
|
||||
const encodedFilePath = fileInfo.download_path.replace(fileName, encodedFileName);
|
||||
return encodedFilePath;
|
||||
}
|
||||
};
|
||||
|
||||
linkify = (text) => {
|
||||
linkify = text => {
|
||||
let linkifiedContent = [];
|
||||
let lines = text.split(/\n/g);
|
||||
linkifiedContent = lines.map((line, i) => {
|
||||
let tokens = line.split(/\s/g);
|
||||
let lineContent = tokens.length === 0 ? '' : tokens.map((token, j) => {
|
||||
let hasSpace = j !== (tokens.length - 1);
|
||||
let space = hasSpace ? ' ' : '';
|
||||
let lineContent =
|
||||
tokens.length === 0
|
||||
? ''
|
||||
: tokens.map((token, j) => {
|
||||
let hasSpace = j !== tokens.length - 1;
|
||||
let space = hasSpace ? ' ' : '';
|
||||
|
||||
if (token.match(/^(lbry|https?):\/\//g)) {
|
||||
return (
|
||||
<Link key={j}
|
||||
style={filePageStyle.link}
|
||||
href={token}
|
||||
text={token}
|
||||
effectOnTap={filePageStyle.linkTapped} />
|
||||
);
|
||||
} else {
|
||||
return token + space;
|
||||
}
|
||||
});
|
||||
if (token.match(/^(lbry|https?):\/\//g)) {
|
||||
return (
|
||||
<Link
|
||||
key={j}
|
||||
style={filePageStyle.link}
|
||||
href={token}
|
||||
text={token}
|
||||
effectOnTap={filePageStyle.linkTapped}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return token + space;
|
||||
}
|
||||
});
|
||||
|
||||
lineContent.push("\n");
|
||||
return (<Text key={i}>{lineContent}</Text>);
|
||||
lineContent.push('\n');
|
||||
return <Text key={i}>{lineContent}</Text>;
|
||||
});
|
||||
|
||||
return linkifiedContent;
|
||||
}
|
||||
};
|
||||
|
||||
checkOrientation = () => {
|
||||
if (this.state.fullscreenMode) {
|
||||
|
@ -405,16 +419,18 @@ class FilePage extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (isLandscape) {
|
||||
this.playerBackground.setNativeProps({ height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height });
|
||||
this.playerBackground.setNativeProps({
|
||||
height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height,
|
||||
});
|
||||
} else if (this.state.playerBgHeight > 0) {
|
||||
this.playerBackground.setNativeProps({ height: this.state.playerBgHeight });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMediaLoaded = (channelName, title, uri) => {
|
||||
this.setState({ mediaLoaded: true });
|
||||
window.currentMediaInfo = { channel: channelName, title, uri };
|
||||
}
|
||||
};
|
||||
|
||||
onPlaybackStarted = () => {
|
||||
let timeToStartMillis, timeToStart;
|
||||
|
@ -428,25 +444,25 @@ class FilePage extends React.PureComponent {
|
|||
const { uri } = navigation.state.params;
|
||||
this.logFileView(uri, claim, timeToStartMillis);
|
||||
|
||||
let payload = { 'uri': uri };
|
||||
let payload = { uri: uri };
|
||||
if (!isNaN(timeToStart)) {
|
||||
payload['time_to_start_seconds'] = timeToStart;
|
||||
payload['time_to_start_ms'] = timeToStartMillis;
|
||||
}
|
||||
NativeModules.Firebase.track('play', payload);
|
||||
}
|
||||
};
|
||||
|
||||
onPlaybackFinished = () => {
|
||||
if (this.scrollView && this.state.relatedContentY) {
|
||||
this.scrollView.scrollTo({ x: 0, y: this.state.relatedContentY, animated: true});
|
||||
this.scrollView.scrollTo({ x: 0, y: this.state.relatedContentY, animated: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setRelatedContentPosition = (evt) => {
|
||||
setRelatedContentPosition = evt => {
|
||||
if (!this.state.relatedContentY) {
|
||||
this.setState({ relatedContentY: evt.nativeEvent.layout.y });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
logFileView = (uri, claim, timeToStart) => {
|
||||
if (!claim) {
|
||||
|
@ -458,7 +474,7 @@ class FilePage extends React.PureComponent {
|
|||
const params = {
|
||||
uri,
|
||||
outpoint,
|
||||
claim_id: claimId
|
||||
claim_id: claimId,
|
||||
};
|
||||
if (!isNaN(timeToStart)) {
|
||||
params.time_to_start = timeToStart;
|
||||
|
@ -466,7 +482,7 @@ class FilePage extends React.PureComponent {
|
|||
|
||||
Lbryio.call('file', 'view', params).catch(() => {});
|
||||
this.setState({ fileViewLogged: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleSendTip = () => {
|
||||
const { claim, balance, navigation, notify, sendTip } = this.props;
|
||||
|
@ -480,26 +496,30 @@ class FilePage extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
sendTip(tipAmount, claim.claim_id, uri, () => { this.setState({ tipAmount: 0, showTipView: false }) });
|
||||
}
|
||||
sendTip(tipAmount, claim.claim_id, uri, () => {
|
||||
this.setState({ tipAmount: 0, showTipView: false });
|
||||
});
|
||||
};
|
||||
|
||||
renderTags = (tags) => {
|
||||
renderTags = tags => {
|
||||
return tags.map((tag, i) => (
|
||||
<Text style={filePageStyle.tagItem} key={`${tag}-${i}`}>{tag}</Text>
|
||||
<Text style={filePageStyle.tagItem} key={`${tag}-${i}`}>
|
||||
{tag}
|
||||
</Text>
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
onFileDownloadButtonPlayed = () => {
|
||||
const { setPlayerVisible } = this.props;
|
||||
this.startTime = Date.now();
|
||||
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
|
||||
setPlayerVisible();
|
||||
}
|
||||
};
|
||||
|
||||
onBackButtonPressed = () => {
|
||||
const { navigation, drawerStack, popDrawerStack } = this.props;
|
||||
navigateBack(navigation, drawerStack, popDrawerStack);
|
||||
}
|
||||
};
|
||||
|
||||
onSaveFilePressed = () => {
|
||||
const { costInfo, fileGet, fileInfo, navigation, purchasedUris, purchaseUri } = this.props;
|
||||
|
@ -509,13 +529,16 @@ class FilePage extends React.PureComponent {
|
|||
// file already in library or URI already purchased, use fileGet directly
|
||||
this.setState({ fileGetStarted: true }, () => fileGet(uri, true));
|
||||
} else {
|
||||
this.setState({
|
||||
downloadPressed: true,
|
||||
autoPlayMedia: false,
|
||||
stopDownloadConfirmed: false
|
||||
}, () => purchaseUri(uri, costInfo, true));
|
||||
this.setState(
|
||||
{
|
||||
downloadPressed: true,
|
||||
autoPlayMedia: false,
|
||||
stopDownloadConfirmed: false,
|
||||
},
|
||||
() => purchaseUri(uri, costInfo, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -534,7 +557,7 @@ class FilePage extends React.PureComponent {
|
|||
position,
|
||||
purchaseUri,
|
||||
thumbnail,
|
||||
title
|
||||
title,
|
||||
} = this.props;
|
||||
const { uri, autoplay } = navigation.state.params;
|
||||
|
||||
|
@ -542,23 +565,22 @@ class FilePage extends React.PureComponent {
|
|||
if ((isResolvingUri && !claim) || !claim) {
|
||||
innerContent = (
|
||||
<View style={filePageStyle.container}>
|
||||
{isResolvingUri &&
|
||||
<View style={filePageStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
|
||||
</View>}
|
||||
{claim === null && !isResolvingUri &&
|
||||
{isResolvingUri && (
|
||||
<View style={filePageStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||
<Text style={filePageStyle.infoText}>Loading decentralized data...</Text>
|
||||
</View>
|
||||
)}
|
||||
{claim === null && !isResolvingUri && (
|
||||
<View style={filePageStyle.container}>
|
||||
<Text style={filePageStyle.emptyClaimText}>There's nothing at this location.</Text>
|
||||
</View>
|
||||
}
|
||||
)}
|
||||
<UriBar value={uri} navigation={navigation} />
|
||||
</View>
|
||||
);
|
||||
} else if (claim && claim.name.length && claim.name[0] === '@') {
|
||||
innerContent = (
|
||||
<ChannelPage uri={uri} navigation={navigation} />
|
||||
);
|
||||
innerContent = <ChannelPage uri={uri} navigation={navigation} />;
|
||||
} else if (claim) {
|
||||
let isClaimBlackListed = false;
|
||||
|
||||
|
@ -577,7 +599,8 @@ class FilePage extends React.PureComponent {
|
|||
<View style={filePageStyle.pageContainer}>
|
||||
<View style={filePageStyle.dmcaContainer}>
|
||||
<Text style={filePageStyle.dmcaText}>
|
||||
In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.
|
||||
In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked
|
||||
access to this content from our applications.
|
||||
</Text>
|
||||
<Link style={filePageStyle.dmcaLink} href="https://lbry.com/faq/dmca" text="Read More" />
|
||||
</View>
|
||||
|
@ -585,7 +608,6 @@ class FilePage extends React.PureComponent {
|
|||
</View>
|
||||
);
|
||||
} else {
|
||||
|
||||
let tags = [];
|
||||
if (claim && claim.value && claim.value.tags) {
|
||||
tags = claim.value.tags;
|
||||
|
@ -597,27 +619,38 @@ class FilePage extends React.PureComponent {
|
|||
const mediaType = Lbry.getMediaType(contentType);
|
||||
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
||||
const { height, channel_name: channelName, value } = claim;
|
||||
const showActions = (fileInfo && fileInfo.download_path) &&
|
||||
const showActions =
|
||||
fileInfo &&
|
||||
fileInfo.download_path &&
|
||||
!this.state.fullscreenMode &&
|
||||
!this.state.showImageViewer &&
|
||||
!this.state.showWebView;
|
||||
const showFileActions = (fileInfo && fileInfo.download_path) &&
|
||||
const showFileActions =
|
||||
fileInfo &&
|
||||
fileInfo.download_path &&
|
||||
(completed || (fileInfo && !fileInfo.stopped && fileInfo.written_bytes < fileInfo.total_bytes));
|
||||
const channelClaimId = claim && claim.signing_channel && claim.signing_channel.claim_id;
|
||||
const canSendTip = this.state.tipAmount > 0;
|
||||
const fullChannelUri = channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
|
||||
const fullChannelUri =
|
||||
channelClaimId && channelClaimId.trim().length > 0 ? `${channelName}#${channelClaimId}` : channelName;
|
||||
|
||||
const playerStyle = [filePageStyle.player,
|
||||
this.state.isLandscape ? filePageStyle.containedPlayerLandscape :
|
||||
(this.state.fullscreenMode ? filePageStyle.fullscreenPlayer : filePageStyle.containedPlayer)];
|
||||
const playerStyle = [
|
||||
filePageStyle.player,
|
||||
this.state.isLandscape
|
||||
? filePageStyle.containedPlayerLandscape
|
||||
: this.state.fullscreenMode
|
||||
? filePageStyle.fullscreenPlayer
|
||||
: filePageStyle.containedPlayer,
|
||||
];
|
||||
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
|
||||
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
|
||||
// at least 2MB (or the full download) before media can be loaded
|
||||
const canLoadMedia = (this.state.streamingMode) || (fileInfo &&
|
||||
(fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes)); // 2MB = 1024*1024*2
|
||||
const isViewable = (mediaType === 'image' || mediaType === 'text');
|
||||
const canLoadMedia =
|
||||
this.state.streamingMode ||
|
||||
(fileInfo && (fileInfo.written_bytes >= 2097152 || fileInfo.written_bytes == fileInfo.total_bytes)); // 2MB = 1024*1024*2
|
||||
const isViewable = mediaType === 'image' || mediaType === 'text';
|
||||
const isWebViewable = mediaType === 'text';
|
||||
const canOpen = isViewable && completed;
|
||||
const canOpen = isViewable && completed;
|
||||
const localFileUri = this.localUriForFileInfo(fileInfo);
|
||||
|
||||
const openFile = () => {
|
||||
|
@ -625,10 +658,12 @@ class FilePage extends React.PureComponent {
|
|||
// use image viewer
|
||||
if (!this.state.showImageViewer) {
|
||||
this.setState({
|
||||
imageUrls: [{
|
||||
url: localFileUri
|
||||
}],
|
||||
showImageViewer: true
|
||||
imageUrls: [
|
||||
{
|
||||
url: localFileUri,
|
||||
},
|
||||
],
|
||||
showImageViewer: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -636,13 +671,18 @@ class FilePage extends React.PureComponent {
|
|||
// show webview
|
||||
if (!this.state.showWebView) {
|
||||
this.setState({
|
||||
showWebView: true
|
||||
showWebView: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && 'true' === this.state.uriVars.download) {
|
||||
if (
|
||||
fileInfo &&
|
||||
!this.state.autoDownloadStarted &&
|
||||
this.state.uriVars &&
|
||||
'true' === this.state.uriVars.download
|
||||
) {
|
||||
this.setState({ autoDownloadStarted: true }, () => {
|
||||
purchaseUri(uri, costInfo, !isPlayable);
|
||||
if (NativeModules.UtilityModule) {
|
||||
|
@ -659,57 +699,86 @@ class FilePage extends React.PureComponent {
|
|||
innerContent = (
|
||||
<View style={filePageStyle.pageContainer}>
|
||||
{!this.state.fullscreenMode && <UriBar value={uri} navigation={navigation} />}
|
||||
{this.state.showWebView && isWebViewable && <WebView source={{ uri: localFileUri }}
|
||||
style={filePageStyle.viewer} />}
|
||||
{this.state.showWebView && isWebViewable && (
|
||||
<WebView source={{ uri: localFileUri }} style={filePageStyle.viewer} />
|
||||
)}
|
||||
|
||||
{this.state.showImageViewer && <ImageViewer style={StyleSheet.flatten(filePageStyle.viewer)}
|
||||
imageUrls={this.state.imageUrls}
|
||||
renderIndicator={() => null} />}
|
||||
{this.state.showImageViewer && (
|
||||
<ImageViewer
|
||||
style={StyleSheet.flatten(filePageStyle.viewer)}
|
||||
imageUrls={this.state.imageUrls}
|
||||
renderIndicator={() => null}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!this.state.showWebView && (
|
||||
<View style={this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer}
|
||||
onLayout={this.checkOrientation}>
|
||||
<View
|
||||
style={
|
||||
this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer
|
||||
}
|
||||
onLayout={this.checkOrientation}
|
||||
>
|
||||
<View style={filePageStyle.mediaContainer}>
|
||||
{((canOpen || (!fileInfo || (isPlayable && !canLoadMedia))) || (!canOpen && fileInfo)) &&
|
||||
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />}
|
||||
{((!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded) &&
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />}
|
||||
{((isPlayable && !completed && !canLoadMedia) || canOpen || (!completed && !this.state.streamingMode)) &&
|
||||
(!this.state.downloadPressed) &&
|
||||
<FileDownloadButton uri={uri}
|
||||
style={filePageStyle.downloadButton}
|
||||
openFile={openFile}
|
||||
isPlayable={isPlayable}
|
||||
isViewable={isViewable}
|
||||
onPlay={this.onFileDownloadButtonPlayed}
|
||||
onView={() => this.setState({ downloadPressed: true })}
|
||||
onButtonLayout={() => this.setState({ downloadButtonShown: true })} />}
|
||||
{!fileInfo && <FilePrice uri={uri} style={filePageStyle.filePriceContainer} textStyle={filePageStyle.filePriceText} />}
|
||||
{(canOpen || (!fileInfo || (isPlayable && !canLoadMedia)) || (!canOpen && fileInfo)) && (
|
||||
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={thumbnail} />
|
||||
)}
|
||||
{(!this.state.downloadButtonShown || this.state.downloadPressed) && !this.state.mediaLoaded && (
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} style={filePageStyle.loading} />
|
||||
)}
|
||||
{((isPlayable && !completed && !canLoadMedia) ||
|
||||
canOpen ||
|
||||
(!completed && !this.state.streamingMode)) &&
|
||||
!this.state.downloadPressed && (
|
||||
<FileDownloadButton
|
||||
uri={uri}
|
||||
style={filePageStyle.downloadButton}
|
||||
openFile={openFile}
|
||||
isPlayable={isPlayable}
|
||||
isViewable={isViewable}
|
||||
onPlay={this.onFileDownloadButtonPlayed}
|
||||
onView={() => this.setState({ downloadPressed: true })}
|
||||
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
|
||||
/>
|
||||
)}
|
||||
{!fileInfo && (
|
||||
<FilePrice
|
||||
uri={uri}
|
||||
style={filePageStyle.filePriceContainer}
|
||||
textStyle={filePageStyle.filePriceText}
|
||||
/>
|
||||
)}
|
||||
|
||||
<TouchableOpacity style={filePageStyle.backButton} onPress={this.onBackButtonPressed}>
|
||||
<Icon name={"arrow-left"} size={18} style={filePageStyle.backButtonIcon} />
|
||||
<Icon name={'arrow-left'} size={18} style={filePageStyle.backButtonIcon} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
|
||||
<View
|
||||
style={playerBgStyle}
|
||||
ref={ref => {
|
||||
this.playerBackground = ref;
|
||||
}}
|
||||
onLayout={evt => {
|
||||
if (!this.state.playerBgHeight) {
|
||||
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
|
||||
<View style={playerBgStyle}
|
||||
ref={(ref) => { this.playerBackground = ref; }}
|
||||
onLayout={(evt) => {
|
||||
if (!this.state.playerBgHeight) {
|
||||
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
|
||||
}
|
||||
}} />}
|
||||
{((this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && this.state.fullscreenMode) &&
|
||||
<View style={fsPlayerBgStyle} />}
|
||||
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) &&
|
||||
this.state.fullscreenMode && <View style={fsPlayerBgStyle} />}
|
||||
{(this.state.streamingMode || (canLoadMedia && fileInfo && isPlayable)) && (
|
||||
<MediaPlayer
|
||||
claim={claim}
|
||||
assignPlayer={(ref) => { this.player = ref; }}
|
||||
assignPlayer={ref => {
|
||||
this.player = ref;
|
||||
}}
|
||||
uri={uri}
|
||||
source={this.playerUriForFileInfo(fileInfo)}
|
||||
style={playerStyle}
|
||||
autoPlay={autoplay || this.state.autoPlayMedia}
|
||||
onFullscreenToggled={this.handleFullscreenToggle}
|
||||
onLayout={(evt) => {
|
||||
onLayout={evt => {
|
||||
if (!this.state.playerHeight) {
|
||||
this.setState({ playerHeight: evt.nativeEvent.layout.height });
|
||||
}
|
||||
|
@ -720,125 +789,168 @@ class FilePage extends React.PureComponent {
|
|||
onPlaybackFinished={this.onPlaybackFinished}
|
||||
thumbnail={thumbnail}
|
||||
position={position}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(showActions && showFileActions) &&
|
||||
<View style={filePageStyle.actions}>
|
||||
{showFileActions &&
|
||||
<View style={filePageStyle.fileActions}>
|
||||
{completed && <Button style={filePageStyle.actionButton}
|
||||
theme={"light"}
|
||||
icon={"trash"}
|
||||
text={"Delete"}
|
||||
onPress={this.onDeletePressed} />}
|
||||
{!completed && fileInfo && !fileInfo.stopped &&
|
||||
fileInfo.written_bytes < fileInfo.total_bytes &&
|
||||
!this.state.stopDownloadConfirmed &&
|
||||
<Button style={filePageStyle.actionButton}
|
||||
icon={"stop"}
|
||||
theme={"light"}
|
||||
text={"Stop Download"}
|
||||
onPress={this.onStopDownloadPressed} />
|
||||
}
|
||||
</View>}
|
||||
</View>}
|
||||
{showActions && showFileActions && (
|
||||
<View style={filePageStyle.actions}>
|
||||
{showFileActions && (
|
||||
<View style={filePageStyle.fileActions}>
|
||||
{completed && (
|
||||
<Button
|
||||
style={filePageStyle.actionButton}
|
||||
theme={'light'}
|
||||
icon={'trash'}
|
||||
text={'Delete'}
|
||||
onPress={this.onDeletePressed}
|
||||
/>
|
||||
)}
|
||||
{!completed &&
|
||||
fileInfo &&
|
||||
!fileInfo.stopped &&
|
||||
fileInfo.written_bytes < fileInfo.total_bytes &&
|
||||
!this.state.stopDownloadConfirmed && (
|
||||
<Button
|
||||
style={filePageStyle.actionButton}
|
||||
icon={'stop'}
|
||||
theme={'light'}
|
||||
text={'Stop Download'}
|
||||
onPress={this.onStopDownloadPressed}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<ScrollView
|
||||
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
|
||||
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
ref={(ref) => { this.scrollView = ref; }}>
|
||||
<TouchableWithoutFeedback style={filePageStyle.titleTouch}
|
||||
onPress={() => this.setState({ showDescription: !this.state.showDescription })}>
|
||||
ref={ref => {
|
||||
this.scrollView = ref;
|
||||
}}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
style={filePageStyle.titleTouch}
|
||||
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
|
||||
>
|
||||
<View style={filePageStyle.titleRow}>
|
||||
<Text style={filePageStyle.title} selectable={true}>{title}</Text>
|
||||
<Text style={filePageStyle.title} selectable={true}>
|
||||
{title}
|
||||
</Text>
|
||||
<View style={filePageStyle.descriptionToggle}>
|
||||
<Icon name={this.state.showDescription ? "caret-up" : "caret-down"} size={24} />
|
||||
<Icon name={this.state.showDescription ? 'caret-up' : 'caret-down'} size={24} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
{channelName &&
|
||||
{channelName && (
|
||||
<View style={filePageStyle.channelRow}>
|
||||
<View style={filePageStyle.publishInfo}>
|
||||
<Link style={filePageStyle.channelName}
|
||||
selectable={true}
|
||||
text={channelName}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode={"tail"}
|
||||
onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}} />
|
||||
<Link
|
||||
style={filePageStyle.channelName}
|
||||
selectable={true}
|
||||
text={channelName}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode={'tail'}
|
||||
onPress={() => {
|
||||
navigateToUri(navigation, normalizeURI(fullChannelUri));
|
||||
}}
|
||||
/>
|
||||
<DateTime
|
||||
style={filePageStyle.publishDate}
|
||||
textStyle={filePageStyle.publishDateText}
|
||||
uri={uri}
|
||||
formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}
|
||||
show={DateTime.SHOW_DATE} />
|
||||
show={DateTime.SHOW_DATE}
|
||||
/>
|
||||
</View>
|
||||
<View style={filePageStyle.subscriptionRow}>
|
||||
{((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) &&
|
||||
<Button style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
|
||||
theme={"light"}
|
||||
icon={"download"}
|
||||
onPress={this.onSaveFilePressed} />}
|
||||
<Button style={[filePageStyle.actionButton, filePageStyle.tipButton]}
|
||||
theme={"light"}
|
||||
icon={"gift"}
|
||||
onPress={() => this.setState({ showTipView: true })} />
|
||||
{((isPlayable && !fileInfo) || (isPlayable && fileInfo && !fileInfo.download_path)) && (
|
||||
<Button
|
||||
style={[filePageStyle.actionButton, filePageStyle.saveFileButton]}
|
||||
theme={'light'}
|
||||
icon={'download'}
|
||||
onPress={this.onSaveFilePressed}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
style={[filePageStyle.actionButton, filePageStyle.tipButton]}
|
||||
theme={'light'}
|
||||
icon={'gift'}
|
||||
onPress={() => this.setState({ showTipView: true })}
|
||||
/>
|
||||
<SubscribeButton
|
||||
style={filePageStyle.actionButton}
|
||||
uri={fullChannelUri}
|
||||
name={channelName}
|
||||
hideText={false} />
|
||||
hideText={false}
|
||||
/>
|
||||
<SubscribeNotificationButton
|
||||
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
|
||||
uri={fullChannelUri}
|
||||
name={channelName} />
|
||||
name={channelName}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
)}
|
||||
|
||||
{this.state.showTipView && <View style={filePageStyle.divider} />}
|
||||
{this.state.showTipView &&
|
||||
<View style={filePageStyle.tipCard}>
|
||||
<View style={filePageStyle.row}>
|
||||
<View style={filePageStyle.amountRow}>
|
||||
<TextInput ref={ref => this.tipAmountInput = ref}
|
||||
onChangeText={value => this.setState({tipAmount: value})}
|
||||
keyboardType={'numeric'}
|
||||
placeholder={'0'}
|
||||
value={this.state.tipAmount}
|
||||
style={[filePageStyle.input, filePageStyle.tipAmountInput]} />
|
||||
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
|
||||
{this.state.showTipView && (
|
||||
<View style={filePageStyle.tipCard}>
|
||||
<View style={filePageStyle.row}>
|
||||
<View style={filePageStyle.amountRow}>
|
||||
<TextInput
|
||||
ref={ref => (this.tipAmountInput = ref)}
|
||||
onChangeText={value => this.setState({ tipAmount: value })}
|
||||
keyboardType={'numeric'}
|
||||
placeholder={'0'}
|
||||
value={this.state.tipAmount}
|
||||
style={[filePageStyle.input, filePageStyle.tipAmountInput]}
|
||||
/>
|
||||
<Text style={[filePageStyle.text, filePageStyle.currency]}>LBC</Text>
|
||||
</View>
|
||||
<Link
|
||||
style={[filePageStyle.link, filePageStyle.cancelTipLink]}
|
||||
text={'Cancel'}
|
||||
onPress={() => this.setState({ showTipView: false })}
|
||||
/>
|
||||
<Button
|
||||
text={'Send a tip'}
|
||||
style={[filePageStyle.button, filePageStyle.sendButton]}
|
||||
disabled={!canSendTip}
|
||||
onPress={this.handleSendTip}
|
||||
/>
|
||||
</View>
|
||||
<Link style={[filePageStyle.link, filePageStyle.cancelTipLink]} text={'Cancel'} onPress={() => this.setState({ showTipView: false })} />
|
||||
<Button text={'Send a tip'}
|
||||
style={[filePageStyle.button, filePageStyle.sendButton]}
|
||||
disabled={!canSendTip}
|
||||
onPress={this.handleSendTip} />
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
|
||||
{(this.state.showDescription && description && description.length > 0) && <View style={filePageStyle.divider} />}
|
||||
{(this.state.showDescription && description) && (
|
||||
{this.state.showDescription && description && description.length > 0 && (
|
||||
<View style={filePageStyle.divider} />
|
||||
)}
|
||||
{this.state.showDescription && description && (
|
||||
<View>
|
||||
<Text style={filePageStyle.description} selectable={true}>{this.linkify(description)}</Text>
|
||||
<Text style={filePageStyle.description} selectable={true}>
|
||||
{this.linkify(description)}
|
||||
</Text>
|
||||
{tags && tags.length > 0 && (
|
||||
<View style={filePageStyle.tagContainer}>
|
||||
<Text style={filePageStyle.tagTitle}>Tags</Text>
|
||||
<View style={filePageStyle.tagList}>{this.renderTags(tags)}</View>
|
||||
</View>
|
||||
)}
|
||||
</View>)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(costInfo && parseFloat(costInfo.cost) > balance) && <FileRewardsDriver navigation={navigation} />}
|
||||
{costInfo && parseFloat(costInfo.cost) > balance && <FileRewardsDriver navigation={navigation} />}
|
||||
|
||||
<View onLayout={this.setRelatedContentPosition} />
|
||||
<RelatedContent navigation={navigation} uri={uri} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
{(!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView) &&
|
||||
<FloatingWalletBalance navigation={navigation} />}
|
||||
{!this.state.fullscreenMode && !this.state.showImageViewer && !this.state.showWebView && (
|
||||
<FloatingWalletBalance navigation={navigation} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import FirstRun from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
const select = state => ({
|
||||
authenticating: selectAuthenticationIsPending(state),
|
||||
authToken: selectAuthToken(state),
|
||||
emailToVerify: selectEmailToVerify(state),
|
||||
|
@ -48,7 +48,10 @@ const perform = dispatch => ({
|
|||
checkSync: () => dispatch(doCheckSync()),
|
||||
setDefaultAccount: () => dispatch(doSetDefaultAccount()),
|
||||
notify: data => dispatch(doToast(data)),
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email))
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FirstRun);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FirstRun);
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
NativeModules,
|
||||
Platform,
|
||||
Text,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { NativeModules, Platform, Text, TextInput, View } from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
|
@ -16,7 +10,7 @@ class EmailCollectPage extends React.PureComponent {
|
|||
state = {
|
||||
email: null,
|
||||
placeholder: 'you@example.com',
|
||||
verifying: true
|
||||
verifying: true,
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
@ -34,16 +28,16 @@ class EmailCollectPage extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleChangeText = (text) => {
|
||||
handleChangeText = text => {
|
||||
// save the value to the state email
|
||||
const { onEmailChanged } = this.props;
|
||||
this.setState({ email: text });
|
||||
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
|
||||
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
|
||||
if (onEmailChanged) {
|
||||
onEmailChanged(text);
|
||||
}
|
||||
AsyncStorage.setItem(Constants.KEY_FIRST_RUN_EMAIL, text);
|
||||
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onEmailViewLayout } = this.props;
|
||||
|
@ -51,33 +45,34 @@ class EmailCollectPage extends React.PureComponent {
|
|||
const content = (
|
||||
<View onLayout={onEmailViewLayout}>
|
||||
<Text style={firstRunStyle.title}>Setup account</Text>
|
||||
<TextInput style={firstRunStyle.emailInput}
|
||||
placeholder={this.state.placeholder}
|
||||
underlineColorAndroid="transparent"
|
||||
selectionColor={Colors.NextLbryGreen}
|
||||
value={this.state.email}
|
||||
onChangeText={text => this.handleChangeText(text)}
|
||||
onFocus={() => {
|
||||
if (!this.state.email || this.state.email.length === 0) {
|
||||
this.setState({ placeholder: '' });
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!this.state.email || this.state.email.length === 0) {
|
||||
this.setState({ placeholder: 'you@example.com' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text style={firstRunStyle.paragraph}>An account will allow you to earn rewards and keep your account and settings synced.</Text>
|
||||
<Text style={firstRunStyle.infoParagraph}>This information is disclosed only to LBRY, Inc. and not to the LBRY network.</Text>
|
||||
<TextInput
|
||||
style={firstRunStyle.emailInput}
|
||||
placeholder={this.state.placeholder}
|
||||
underlineColorAndroid="transparent"
|
||||
selectionColor={Colors.NextLbryGreen}
|
||||
value={this.state.email}
|
||||
onChangeText={text => this.handleChangeText(text)}
|
||||
onFocus={() => {
|
||||
if (!this.state.email || this.state.email.length === 0) {
|
||||
this.setState({ placeholder: '' });
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!this.state.email || this.state.email.length === 0) {
|
||||
this.setState({ placeholder: 'you@example.com' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
An account will allow you to earn rewards and keep your account and settings synced.
|
||||
</Text>
|
||||
<Text style={firstRunStyle.infoParagraph}>
|
||||
This information is disclosed only to LBRY, Inc. and not to the LBRY network.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
return <View style={firstRunStyle.container}>{content}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
NativeModules,
|
||||
Platform,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Linking, NativeModules, Platform, Switch, Text, TextInput, View } from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import Button from 'component/button';
|
||||
import Colors from 'styles/colors';
|
||||
|
@ -23,7 +14,7 @@ class EmailVerifyPage extends React.PureComponent {
|
|||
resendVerificationEmail(email);
|
||||
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'true');
|
||||
notify({ message: 'Please follow the instructions in the email sent to your address to continue.' });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onEmailViewLayout, email } = this.props;
|
||||
|
@ -31,19 +22,26 @@ class EmailVerifyPage extends React.PureComponent {
|
|||
const content = (
|
||||
<View onLayout={onEmailViewLayout}>
|
||||
<Text style={firstRunStyle.title}>Verify Email</Text>
|
||||
<Text style={firstRunStyle.paragraph}>An email has been sent to <Text style={firstRunStyle.nowrap} numberOfLines={1}>{email}</Text>. Please follow the instructions in the message to verify your email address.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
An email has been sent to{' '}
|
||||
<Text style={firstRunStyle.nowrap} numberOfLines={1}>
|
||||
{email}
|
||||
</Text>
|
||||
. Please follow the instructions in the message to verify your email address.
|
||||
</Text>
|
||||
|
||||
<View style={firstRunStyle.buttonContainer}>
|
||||
<Button style={firstRunStyle.verificationButton} theme={"light"} text={"Resend"} onPress={this.onResendPressed} />
|
||||
<Button
|
||||
style={firstRunStyle.verificationButton}
|
||||
theme={'light'}
|
||||
text={'Resend'}
|
||||
onPress={this.onResendPressed}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
return <View style={firstRunStyle.container}>{content}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
View
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
|
@ -18,7 +18,7 @@ import firstRunStyle from 'styles/firstRun';
|
|||
|
||||
class SkipAccountPage extends React.PureComponent {
|
||||
state = {
|
||||
confirmed: false
|
||||
confirmed: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -30,22 +30,29 @@ class SkipAccountPage extends React.PureComponent {
|
|||
<Icon name="exclamation-triangle" style={firstRunStyle.titleIcon} size={32} color={Colors.White} />
|
||||
<Text style={firstRunStyle.title}>Are you sure?</Text>
|
||||
</View>
|
||||
<Text style={firstRunStyle.paragraph}>Without an account, you will not receive rewards, sync and backup services, or security updates.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
Without an account, you will not receive rewards, sync and backup services, or security updates.
|
||||
</Text>
|
||||
|
||||
<View style={[firstRunStyle.row, firstRunStyle.confirmContainer]}>
|
||||
<View style={firstRunStyle.rowSwitch}>
|
||||
<Switch value={this.state.confirmed} onValueChange={value => { this.setState({ confirmed: value }); onSkipSwitchChanged(value); }} />
|
||||
<Switch
|
||||
value={this.state.confirmed}
|
||||
onValueChange={value => {
|
||||
this.setState({ confirmed: value });
|
||||
onSkipSwitchChanged(value);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Text style={firstRunStyle.rowParagraph}>I understand that by uninstalling LBRY I will lose any balances or published content with no recovery option.</Text>
|
||||
<Text style={firstRunStyle.rowParagraph}>
|
||||
I understand that by uninstalling LBRY I will lose any balances or published content with no recovery
|
||||
option.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
return <View style={firstRunStyle.container}>{content}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { BarPasswordStrengthDisplay } from 'react-native-password-strength-meter';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
|
@ -27,7 +27,7 @@ class WalletPage extends React.PureComponent {
|
|||
statusTries: 0,
|
||||
walletReady: false,
|
||||
hasCheckedSync: false,
|
||||
revealPassword: false
|
||||
revealPassword: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -36,28 +36,30 @@ class WalletPage extends React.PureComponent {
|
|||
|
||||
checkWalletReady = () => {
|
||||
// make sure the sdk wallet component is ready
|
||||
Lbry.status().then(status => {
|
||||
if (status.startup_status && status.startup_status.wallet) {
|
||||
this.setState({ walletReady: true }, () => {
|
||||
this.props.checkSync();
|
||||
setTimeout(() => this.setState({ hasCheckedSync: true}), 1000);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTimeout(this.checkWalletReady, 1000);
|
||||
}).catch((e) => {
|
||||
setTimeout(this.checkWalletReady, 1000);
|
||||
});
|
||||
}
|
||||
Lbry.status()
|
||||
.then(status => {
|
||||
if (status.startup_status && status.startup_status.wallet) {
|
||||
this.setState({ walletReady: true }, () => {
|
||||
this.props.checkSync();
|
||||
setTimeout(() => this.setState({ hasCheckedSync: true }), 1000);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTimeout(this.checkWalletReady, 1000);
|
||||
})
|
||||
.catch(e => {
|
||||
setTimeout(this.checkWalletReady, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
handleChangeText = (text) => {
|
||||
handleChangeText = text => {
|
||||
// save the value to the state email
|
||||
const { onPasswordChanged } = this.props;
|
||||
this.setState({ password: text });
|
||||
if (onPasswordChanged) {
|
||||
onPasswordChanged(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onPasswordChanged, onWalletViewLayout, getSyncIsPending, hasSyncedWallet, syncApplyIsPending } = this.props;
|
||||
|
@ -74,7 +76,7 @@ class WalletPage extends React.PureComponent {
|
|||
content = (
|
||||
<View style={firstRunStyle.centered}>
|
||||
<ActivityIndicator size="large" color={Colors.White} style={firstRunStyle.waiting} />
|
||||
<Text style={firstRunStyle.paragraph}>Validating password...</Text>
|
||||
<Text style={firstRunStyle.paragraph}>Validating password...</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
|
@ -82,11 +84,13 @@ class WalletPage extends React.PureComponent {
|
|||
<View onLayout={onWalletViewLayout}>
|
||||
<Text style={firstRunStyle.title}>Password</Text>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
{hasSyncedWallet ? "Please enter the password you used to secure your wallet." :
|
||||
"Please enter a password to secure your account and wallet."}
|
||||
{hasSyncedWallet
|
||||
? 'Please enter the password you used to secure your wallet.'
|
||||
: 'Please enter a password to secure your account and wallet.'}
|
||||
</Text>
|
||||
<View style={firstRunStyle.passwordInputContainer}>
|
||||
<TextInput style={firstRunStyle.passwordInput}
|
||||
<TextInput
|
||||
style={firstRunStyle.passwordInput}
|
||||
placeholder={this.state.placeholder}
|
||||
underlineColorAndroid="transparent"
|
||||
selectionColor={Colors.NextLbryGreen}
|
||||
|
@ -103,31 +107,32 @@ class WalletPage extends React.PureComponent {
|
|||
this.setState({ placeholder: 'password' });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={firstRunStyle.revealPasswordIcon}
|
||||
onPress={() => this.setState({ revealPassword: !this.state.revealPassword })}>
|
||||
<Icon name={this.state.revealPassword ? "eye-slash" : "eye"} size={16} style={firstRunStyle.revealIcon} />
|
||||
onPress={() => this.setState({ revealPassword: !this.state.revealPassword })}
|
||||
>
|
||||
<Icon name={this.state.revealPassword ? 'eye-slash' : 'eye'} size={16} style={firstRunStyle.revealIcon} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{(!hasSyncedWallet && this.state.password && this.state.password.trim().length) > 0 &&
|
||||
{(!hasSyncedWallet && this.state.password && this.state.password.trim().length) > 0 && (
|
||||
<View style={firstRunStyle.passwordStrength}>
|
||||
<BarPasswordStrengthDisplay
|
||||
width={Dimensions.get('window').width - firstRunMargins}
|
||||
minLength={1}
|
||||
password={this.state.password} />
|
||||
</View>}
|
||||
<Text style={firstRunStyle.infoParagraph}>Note: for wallet security purposes, LBRY is unable to reset your password.</Text>
|
||||
password={this.state.password}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<Text style={firstRunStyle.infoParagraph}>
|
||||
Note: for wallet security purposes, LBRY is unable to reset your password.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
return <View style={firstRunStyle.container}>{content}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
NativeModules,
|
||||
Platform,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, NativeModules, Platform, Text, View } from 'react-native';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
|
@ -53,25 +47,27 @@ class WelcomePage extends React.PureComponent {
|
|||
const { authenticate } = this.props;
|
||||
this.setState({ authenticationStarted: true, authenticationFailed: false });
|
||||
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
|
||||
Lbry.status().then(info => {
|
||||
this.setState({ sdkStarted: true });
|
||||
Lbry.status()
|
||||
.then(info => {
|
||||
this.setState({ sdkStarted: true });
|
||||
|
||||
authenticate(appVersion, Platform.OS);
|
||||
}).catch(error => {
|
||||
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
|
||||
this.setState({ authenticationFailed: true });
|
||||
authenticate(appVersion, Platform.OS);
|
||||
})
|
||||
.catch(error => {
|
||||
if (this.state.statusTries >= WelcomePage.MAX_STATUS_TRIES) {
|
||||
this.setState({ authenticationFailed: true });
|
||||
|
||||
// sdk_start_failed
|
||||
NativeModules.Firebase.track('sdk_start_failed', null);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.startAuthenticating();
|
||||
this.setState({ statusTries: this.state.statusTries + 1 });
|
||||
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
|
||||
}
|
||||
});
|
||||
// sdk_start_failed
|
||||
NativeModules.Firebase.track('sdk_start_failed', null);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.startAuthenticating();
|
||||
this.setState({ statusTries: this.state.statusTries + 1 });
|
||||
}, 1000); // Retry every second for a maximum of MAX_STATUS_TRIES tries (60 seconds)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { authenticating, authToken, onWelcomePageLayout } = this.props;
|
||||
|
@ -81,7 +77,10 @@ class WelcomePage extends React.PureComponent {
|
|||
// Ask the user to try again
|
||||
content = (
|
||||
<View>
|
||||
<Text style={firstRunStyle.paragraph}>The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the app to try again.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
The LBRY servers were unreachable at this time. Please check your Internet connection and then restart the
|
||||
app to try again.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (!authToken || authenticating) {
|
||||
|
@ -95,16 +94,15 @@ class WelcomePage extends React.PureComponent {
|
|||
content = (
|
||||
<View onLayout={onWelcomePageLayout}>
|
||||
<Text style={firstRunStyle.title}>Welcome to LBRY.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.</Text>
|
||||
<Text style={firstRunStyle.paragraph}>
|
||||
LBRY is a community-controlled content platform where you can find and publish videos, music, books, and
|
||||
more.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.container}>
|
||||
{content}
|
||||
</View>
|
||||
);
|
||||
return <View style={firstRunStyle.container}>{content}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Linking,
|
||||
NativeModules,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Linking, NativeModules, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { NavigationActions, StackActions } from 'react-navigation';
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import Colors from 'styles/colors';
|
||||
|
@ -39,11 +32,11 @@ class FirstRunScreen extends React.PureComponent {
|
|||
skipAccountConfirmed: false,
|
||||
showBottomContainer: false,
|
||||
walletPassword: null,
|
||||
syncApplyStarted: false
|
||||
syncApplyStarted: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
Linking.getInitialURL().then(url => {
|
||||
if (url) {
|
||||
this.setState({ launchUrl: url });
|
||||
}
|
||||
|
@ -72,10 +65,10 @@ class FirstRunScreen extends React.PureComponent {
|
|||
if (this.state.emailSubmitted && !emailNewPending) {
|
||||
this.setState({ emailSubmitted: false });
|
||||
if (emailNewErrorMessage && emailNewErrorMessage.trim().length > 0) {
|
||||
notify ({ message: String(emailNewErrorMessage), isError: true });
|
||||
notify({ message: String(emailNewErrorMessage), isError: true });
|
||||
} else {
|
||||
// Request successful. Navigate to email verify page.
|
||||
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_VERIFY)
|
||||
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_VERIFY);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,25 +91,26 @@ class FirstRunScreen extends React.PureComponent {
|
|||
this.checkVerificationStatus(user);
|
||||
}
|
||||
|
||||
checkVerificationStatus = (user) => {
|
||||
checkVerificationStatus = user => {
|
||||
const { navigation } = this.props;
|
||||
|
||||
this.setState({
|
||||
isEmailVerified: (user && user.primary_email && user.has_verified_email),
|
||||
}, () => {
|
||||
if (this.state.isEmailVerified) {
|
||||
this.showPage(Constants.FIRST_RUN_PAGE_WALLET);
|
||||
this.setState(
|
||||
{
|
||||
isEmailVerified: user && user.primary_email && user.has_verified_email,
|
||||
},
|
||||
() => {
|
||||
if (this.state.isEmailVerified) {
|
||||
this.showPage(Constants.FIRST_RUN_PAGE_WALLET);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
launchSplashScreen() {
|
||||
const { navigation } = this.props;
|
||||
const resetAction = StackActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
NavigationActions.navigate({ routeName: 'Splash', params: { launchUri: this.state.launchUri } })
|
||||
]
|
||||
actions: [NavigationActions.navigate({ routeName: 'Splash', params: { launchUri: this.state.launchUri } })],
|
||||
});
|
||||
navigation.dispatch(resetAction);
|
||||
}
|
||||
|
@ -136,23 +130,19 @@ class FirstRunScreen extends React.PureComponent {
|
|||
if (Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) {
|
||||
this.showPage(Constants.FIRST_RUN_PAGE_EMAIL_COLLECT);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkWalletPassword = () => {
|
||||
const { syncApply, syncHash, syncData } = this.props;
|
||||
this.setState({ syncApplyStarted: true, showBottomContainer: false }, () => {
|
||||
syncApply(syncHash, syncData, this.state.walletPassword);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleContinuePressed = () => {
|
||||
const { notify, user, hasSyncedWallet } = this.props;
|
||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||
if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
|
||||
if (!this.state.walletPassword || this.state.walletPassword.trim().length === 0) {
|
||||
return notify({ message: 'Please enter a wallet password' });
|
||||
}
|
||||
|
||||
// do apply sync to check if the password is valid
|
||||
if (hasSyncedWallet) {
|
||||
this.checkWalletPassword();
|
||||
|
@ -167,7 +157,10 @@ class FirstRunScreen extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage && pageIndex === (FirstRunScreen.pages.length - 1)) {
|
||||
if (
|
||||
Constants.FIRST_RUN_PAGE_EMAIL_COLLECT !== this.state.currentPage &&
|
||||
pageIndex === FirstRunScreen.pages.length - 1
|
||||
) {
|
||||
this.closeFinalPage();
|
||||
} else {
|
||||
// TODO: Actions and page verification for specific pages
|
||||
|
@ -178,37 +171,35 @@ class FirstRunScreen extends React.PureComponent {
|
|||
this.showNextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleEmailCollectPageContinue() {
|
||||
const { notify, addUserEmail } = this.props;
|
||||
const { email } = this.state;
|
||||
// validate the email
|
||||
if (!email || email.indexOf('@') === -1) {
|
||||
return notify({
|
||||
message: 'Please provide a valid email address to continue.',
|
||||
});
|
||||
}
|
||||
|
||||
AsyncStorage.getItem(Constants.KEY_FIRST_RUN_EMAIL).then(email => {
|
||||
// validate the email
|
||||
if (!email || email.indexOf('@') === -1) {
|
||||
return notify({
|
||||
message: 'Please provide a valid email address to continue.',
|
||||
});
|
||||
}
|
||||
|
||||
addUserEmail(email);
|
||||
this.setState({ emailSubmitted: true });
|
||||
});
|
||||
addUserEmail(email);
|
||||
this.setState({ emailSubmitted: true });
|
||||
}
|
||||
|
||||
checkBottomContainer = (pageName) => {
|
||||
checkBottomContainer = pageName => {
|
||||
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === pageName || Constants.FIRST_RUN_PAGE_WALLET === pageName) {
|
||||
// do not show the buttons (because we're waiting to get things ready)
|
||||
this.setState({ showBottomContainer: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
showNextPage = () => {
|
||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||
const nextPage = FirstRunScreen.pages[pageIndex + 1];
|
||||
this.setState({ currentPage: nextPage });
|
||||
this.checkBottomContainer(nextPage);
|
||||
}
|
||||
};
|
||||
|
||||
showPage(pageName) {
|
||||
const pageIndex = FirstRunScreen.pages.indexOf(pageName);
|
||||
|
@ -228,52 +219,50 @@ class FirstRunScreen extends React.PureComponent {
|
|||
this.launchSplashScreen();
|
||||
}
|
||||
|
||||
onEmailChanged = (email) => {
|
||||
onEmailChanged = email => {
|
||||
this.setState({ email });
|
||||
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT == this.state.currentPage) {
|
||||
this.setState({ showSkip: (!email || email.trim().length === 0) });
|
||||
this.setState({ showSkip: !email || email.trim().length === 0 });
|
||||
} else {
|
||||
this.setState({ showSkip: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onEmailViewLayout = () => {
|
||||
this.setState({ showBottomContainer: true });
|
||||
AsyncStorage.getItem('firstRunEmail').then(email => {
|
||||
this.setState({ showSkip: !email || email.trim().length === 0 });
|
||||
});
|
||||
}
|
||||
this.setState({ showBottomContainer: true, showSkip: true });
|
||||
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
|
||||
AsyncStorage.removeItem(Constants.KEY_EMAIL_VERIFY_PENDING);
|
||||
};
|
||||
|
||||
onWalletPasswordChanged = (password) => {
|
||||
onWalletPasswordChanged = password => {
|
||||
this.setState({ walletPassword: password });
|
||||
}
|
||||
};
|
||||
|
||||
onWalletViewLayout = () => {
|
||||
this.setState({ showBottomContainer: true });
|
||||
}
|
||||
};
|
||||
|
||||
onWelcomePageLayout = () => {
|
||||
this.setState({ showBottomContainer: true });
|
||||
}
|
||||
};
|
||||
|
||||
onSkipSwitchChanged = (checked) => {
|
||||
onSkipSwitchChanged = checked => {
|
||||
this.setState({ skipAccountConfirmed: checked });
|
||||
}
|
||||
};
|
||||
|
||||
setFreshPassword = () => {
|
||||
const { getSync, setClientSetting } = this.props;
|
||||
if (NativeModules.UtilityModule) {
|
||||
NativeModules.UtilityModule.setSecureValue(Constants.KEY_FIRST_RUN_PASSWORD, this.state.walletPassword);
|
||||
Lbry.account_encrypt({ new_password: this.state.walletPassword }).then(() => {
|
||||
Lbry.account_unlock({ password: this.state.walletPassword }).then(() => {
|
||||
// fresh account, new password set
|
||||
getSync(this.state.walletPassword);
|
||||
setClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED, true);
|
||||
this.closeFinalPage();
|
||||
});
|
||||
const newPassword = this.state.walletPassword ? this.state.walletPassword : '';
|
||||
NativeModules.UtilityModule.setSecureValue(Constants.KEY_FIRST_RUN_PASSWORD, newPassword);
|
||||
Lbry.account_encrypt({ new_password: newPassword }).then(() => {
|
||||
// fresh account, new password set
|
||||
getSync(newPassword);
|
||||
setClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED, true);
|
||||
this.closeFinalPage();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -289,84 +278,113 @@ class FirstRunScreen extends React.PureComponent {
|
|||
getSyncIsPending,
|
||||
syncApplyIsPending,
|
||||
resendVerificationEmail,
|
||||
user
|
||||
user,
|
||||
} = this.props;
|
||||
|
||||
let page = null;
|
||||
switch (this.state.currentPage) {
|
||||
case Constants.FIRST_RUN_PAGE_WELCOME:
|
||||
page = (<WelcomePage
|
||||
authenticating={authenticating}
|
||||
authToken={authToken}
|
||||
authenticate={authenticate}
|
||||
onWelcomePageLayout={this.onWelcomePageLayout} />);
|
||||
page = (
|
||||
<WelcomePage
|
||||
authenticating={authenticating}
|
||||
authToken={authToken}
|
||||
authenticate={authenticate}
|
||||
onWelcomePageLayout={this.onWelcomePageLayout}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case Constants.FIRST_RUN_PAGE_EMAIL_COLLECT:
|
||||
page = (<EmailCollectPage
|
||||
user={user}
|
||||
showNextPage={this.showNextPage}
|
||||
onEmailChanged={this.onEmailChanged}
|
||||
onEmailViewLayout={this.onEmailViewLayout} />);
|
||||
page = (
|
||||
<EmailCollectPage
|
||||
user={user}
|
||||
showNextPage={this.showNextPage}
|
||||
onEmailChanged={this.onEmailChanged}
|
||||
onEmailViewLayout={this.onEmailViewLayout}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case Constants.FIRST_RUN_PAGE_EMAIL_VERIFY:
|
||||
page = (<EmailVerifyPage
|
||||
onEmailViewLayout={this.onEmailViewLayout}
|
||||
email={this.state.email}
|
||||
notify={notify}
|
||||
resendVerificationEmail={resendVerificationEmail} />);
|
||||
page = (
|
||||
<EmailVerifyPage
|
||||
onEmailViewLayout={this.onEmailViewLayout}
|
||||
email={this.state.email}
|
||||
notify={notify}
|
||||
resendVerificationEmail={resendVerificationEmail}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case Constants.FIRST_RUN_PAGE_WALLET:
|
||||
page = (<WalletPage
|
||||
checkSync={checkSync}
|
||||
hasSyncedWallet={hasSyncedWallet}
|
||||
getSyncIsPending={getSyncIsPending}
|
||||
syncApplyIsPending={syncApplyIsPending}
|
||||
onWalletViewLayout={this.onWalletViewLayout}
|
||||
onPasswordChanged={this.onWalletPasswordChanged} />);
|
||||
page = (
|
||||
<WalletPage
|
||||
checkSync={checkSync}
|
||||
hasSyncedWallet={hasSyncedWallet}
|
||||
getSyncIsPending={getSyncIsPending}
|
||||
syncApplyIsPending={syncApplyIsPending}
|
||||
onWalletViewLayout={this.onWalletViewLayout}
|
||||
onPasswordChanged={this.onWalletPasswordChanged}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT:
|
||||
page = (<SkipAccountPage
|
||||
onSkipAccountViewLayout={this.onSkipAccountViewLayout}
|
||||
onSkipSwitchChanged={this.onSkipSwitchChanged} />);
|
||||
page = (
|
||||
<SkipAccountPage
|
||||
onSkipAccountViewLayout={this.onSkipAccountViewLayout}
|
||||
onSkipSwitchChanged={this.onSkipSwitchChanged}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={firstRunStyle.screenContainer}>
|
||||
{page}
|
||||
{this.state.currentPage && this.state.showBottomContainer &&
|
||||
<View style={firstRunStyle.bottomContainer}>
|
||||
{emailNewPending &&
|
||||
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />}
|
||||
{this.state.currentPage && this.state.showBottomContainer && (
|
||||
<View style={firstRunStyle.bottomContainer}>
|
||||
{emailNewPending && (
|
||||
<ActivityIndicator size="small" color={Colors.White} style={firstRunStyle.pageWaiting} />
|
||||
)}
|
||||
|
||||
<View style={firstRunStyle.buttonRow}>
|
||||
{([Constants.FIRST_RUN_PAGE_WELCOME, Constants.FIRST_RUN_PAGE_WALLET].indexOf(this.state.currentPage) > -1) && <View />}
|
||||
{(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ||
|
||||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) &&
|
||||
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||
<Text style={firstRunStyle.buttonText}>
|
||||
« {Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ? 'Setup account' : 'Change email'}</Text>
|
||||
</TouchableOpacity>}
|
||||
{!emailNewPending && (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) &&
|
||||
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||
<Text style={firstRunStyle.smallLeftButtonText}>No, thanks »</Text>
|
||||
</TouchableOpacity>}
|
||||
<View style={firstRunStyle.buttonRow}>
|
||||
{[Constants.FIRST_RUN_PAGE_WELCOME, Constants.FIRST_RUN_PAGE_WALLET].indexOf(this.state.currentPage) >
|
||||
-1 && <View />}
|
||||
{(Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage ||
|
||||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY === this.state.currentPage) && (
|
||||
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||
<Text style={firstRunStyle.buttonText}>
|
||||
«{' '}
|
||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage
|
||||
? 'Setup account'
|
||||
: 'Change email'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{!emailNewPending && Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage && (
|
||||
<TouchableOpacity style={firstRunStyle.leftButton} onPress={this.handleLeftButtonPressed}>
|
||||
<Text style={firstRunStyle.smallLeftButtonText}>No, thanks »</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{!emailNewPending &&
|
||||
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
|
||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage &&
|
||||
<Text style={firstRunStyle.smallButtonText}>Use LBRY »</Text>}
|
||||
{!emailNewPending && (
|
||||
<TouchableOpacity style={firstRunStyle.button} onPress={this.handleContinuePressed}>
|
||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT === this.state.currentPage && (
|
||||
<Text style={firstRunStyle.smallButtonText}>Use LBRY »</Text>
|
||||
)}
|
||||
|
||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
|
||||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage &&
|
||||
<Text style={firstRunStyle.buttonText}>{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? 'Use LBRY' : 'Continue'} »</Text>}
|
||||
</TouchableOpacity>}
|
||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
|
||||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage && (
|
||||
<Text style={firstRunStyle.buttonText}>
|
||||
{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? 'Use LBRY' : 'Continue'} »
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>}
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@ import PublishPage from './view';
|
|||
const perform = dispatch => ({
|
||||
notify: data => dispatch(doToast(data)),
|
||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH)),
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(PublishPage);
|
||||
export default connect(
|
||||
null,
|
||||
perform
|
||||
)(PublishPage);
|
||||
|
|
|
@ -25,7 +25,7 @@ class PublishPage extends React.PureComponent {
|
|||
NativeModules.Gallery.getVideos().then(videos => {
|
||||
console.log(videos);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -46,7 +46,6 @@ class PublishPage extends React.PureComponent {
|
|||
return (
|
||||
<View style={publishStyle.container}>
|
||||
<UriBar navigation={navigation} />
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,10 @@ const perform = dispatch => ({
|
|||
fetchRewards: () => dispatch(doRewardList()),
|
||||
notify: data => dispatch(doToast(data)),
|
||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_REWARDS)),
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(RewardsPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(RewardsPage);
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
NativeModules,
|
||||
ScrollView,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, NativeModules, ScrollView, Text, View } from 'react-native';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
import Link from 'component/link';
|
||||
|
@ -25,7 +19,7 @@ class RewardsPage extends React.PureComponent {
|
|||
isRewardApproved: false,
|
||||
verifyRequestStarted: false,
|
||||
revealVerification: true,
|
||||
firstRewardClaimed: false
|
||||
firstRewardClaimed: false,
|
||||
};
|
||||
|
||||
scrollView = null;
|
||||
|
@ -51,11 +45,11 @@ class RewardsPage extends React.PureComponent {
|
|||
fetchRewards();
|
||||
|
||||
this.setState({
|
||||
isEmailVerified: (user && user.primary_email && user.has_verified_email),
|
||||
isIdentityVerified: (user && user.is_identity_verified),
|
||||
isRewardApproved: (user && user.is_reward_approved)
|
||||
isEmailVerified: user && user.primary_email && user.has_verified_email,
|
||||
isIdentityVerified: user && user.is_identity_verified,
|
||||
isRewardApproved: user && user.is_reward_approved,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -83,9 +77,9 @@ class RewardsPage extends React.PureComponent {
|
|||
if (user) {
|
||||
// update other checks (if new user data has been retrieved)
|
||||
this.setState({
|
||||
isEmailVerified: (user && user.primary_email && user.has_verified_email),
|
||||
isIdentityVerified: (user && user.is_identity_verified),
|
||||
isRewardApproved: (user && user.is_reward_approved)
|
||||
isEmailVerified: user && user.primary_email && user.has_verified_email,
|
||||
isIdentityVerified: user && user.is_identity_verified,
|
||||
isRewardApproved: user && user.is_reward_approved,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -111,7 +105,13 @@ class RewardsPage extends React.PureComponent {
|
|||
<View style={[rewardStyle.card, rewardStyle.verification]}>
|
||||
<Text style={rewardStyle.title}>Manual Reward Verification</Text>
|
||||
<Text style={rewardStyle.text}>
|
||||
You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.greenLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />.
|
||||
You need to be manually verified before you can start claiming rewards. Please request to be verified on the{' '}
|
||||
<Link
|
||||
style={rewardStyle.greenLink}
|
||||
href="https://discordapp.com/invite/Z3bERWA"
|
||||
text="LBRY Discord server"
|
||||
/>
|
||||
.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
@ -122,7 +122,7 @@ class RewardsPage extends React.PureComponent {
|
|||
|
||||
renderUnclaimedRewards() {
|
||||
const { claimed, fetching, rewards, user } = this.props;
|
||||
const unclaimedRewards = (rewards && rewards.length) ? rewards : [];
|
||||
const unclaimedRewards = rewards && rewards.length ? rewards : [];
|
||||
|
||||
if (fetching) {
|
||||
return (
|
||||
|
@ -142,11 +142,15 @@ class RewardsPage extends React.PureComponent {
|
|||
const isNotEligible = !user || !user.primary_email || !user.has_verified_email || !user.is_reward_approved;
|
||||
return (
|
||||
<View>
|
||||
{unclaimedRewards.map(reward => <RewardCard key={reward.reward_type}
|
||||
showVerification={this.showVerification}
|
||||
canClaim={!isNotEligible}
|
||||
reward={reward}
|
||||
reward_type={reward.reward_type} />)}
|
||||
{unclaimedRewards.map(reward => (
|
||||
<RewardCard
|
||||
key={reward.reward_type}
|
||||
showVerification={this.showVerification}
|
||||
canClaim={!isNotEligible}
|
||||
reward={reward}
|
||||
reward_type={reward.reward_type}
|
||||
/>
|
||||
))}
|
||||
<CustomRewardCard canClaim={!isNotEligible} showVerification={this.showVerification} />
|
||||
</View>
|
||||
);
|
||||
|
@ -158,7 +162,9 @@ class RewardsPage extends React.PureComponent {
|
|||
const reversed = claimed.reverse();
|
||||
return (
|
||||
<View>
|
||||
{reversed.map(reward => <RewardCard key={reward.transaction_id} reward={reward} />)}
|
||||
{reversed.map(reward => (
|
||||
<RewardCard key={reward.transaction_id} reward={reward} />
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -170,7 +176,7 @@ class RewardsPage extends React.PureComponent {
|
|||
this.scrollView.scrollTo({ x: 0, y: 0, animated: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user, navigation } = this.props;
|
||||
|
@ -178,18 +184,19 @@ class RewardsPage extends React.PureComponent {
|
|||
return (
|
||||
<View style={rewardStyle.container}>
|
||||
<UriBar navigation={navigation} />
|
||||
{(!this.state.isEmailVerified || !this.state.isRewardApproved) &&
|
||||
<RewardEnrolment navigation={navigation} />}
|
||||
{(!this.state.isEmailVerified || !this.state.isRewardApproved) && <RewardEnrolment navigation={navigation} />}
|
||||
|
||||
{(this.state.isEmailVerified && this.state.isRewardApproved) &&
|
||||
{this.state.isEmailVerified && this.state.isRewardApproved && (
|
||||
<ScrollView
|
||||
ref={ref => this.scrollView = ref}
|
||||
ref={ref => (this.scrollView = ref)}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
style={rewardStyle.scrollContainer}
|
||||
contentContainerStyle={rewardStyle.scrollContentContainer}>
|
||||
contentContainerStyle={rewardStyle.scrollContentContainer}
|
||||
>
|
||||
{this.renderUnclaimedRewards()}
|
||||
{this.renderClaimedRewards()}
|
||||
</ScrollView>}
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,14 +6,14 @@ import {
|
|||
selectIsSearching,
|
||||
selectSearchValue,
|
||||
makeSelectQueryWithOptions,
|
||||
selectSearchUrisByQuery
|
||||
selectSearchUrisByQuery,
|
||||
} from 'lbry-redux';
|
||||
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||
import Constants from 'constants';
|
||||
import SearchPage from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
const select = state => ({
|
||||
currentRoute: selectCurrentRoute(state),
|
||||
isSearching: selectIsSearching(state),
|
||||
query: selectSearchValue(state),
|
||||
|
@ -22,10 +22,13 @@ const select = (state) => ({
|
|||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
search: (query) => dispatch(doSearch(query, 25)),
|
||||
search: query => dispatch(doSearch(query, 25)),
|
||||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
|
||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_SEARCH)),
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false))
|
||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SearchPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(SearchPage);
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Lbry, parseURI, normalizeURI, isURIValid } from 'lbry-redux';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, Button, Text, TextInput, View, ScrollView } from 'react-native';
|
||||
import { navigateToUri } from 'utils/helper';
|
||||
import Colors from 'styles/colors';
|
||||
import Constants from 'constants';
|
||||
|
@ -21,10 +14,10 @@ class SearchPage extends React.PureComponent {
|
|||
state = {
|
||||
currentQuery: null,
|
||||
currentUri: null,
|
||||
}
|
||||
};
|
||||
|
||||
static navigationOptions = {
|
||||
title: 'Search Results'
|
||||
title: 'Search Results',
|
||||
};
|
||||
|
||||
didFocusListener;
|
||||
|
@ -49,11 +42,11 @@ class SearchPage extends React.PureComponent {
|
|||
if (searchQuery && searchQuery.trim().length > 0) {
|
||||
this.setState({
|
||||
currentQuery: searchQuery,
|
||||
currentUri: (isURIValid(searchQuery)) ? normalizeURI(searchQuery) : null
|
||||
currentUri: isURIValid(searchQuery) ? normalizeURI(searchQuery) : null,
|
||||
});
|
||||
search(searchQuery);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -70,7 +63,7 @@ class SearchPage extends React.PureComponent {
|
|||
if (query && query.trim().length > 0 && query !== this.state.currentQuery) {
|
||||
this.setState({
|
||||
currentQuery: query,
|
||||
currentUri: (isURIValid(query)) ? normalizeURI(query) : null
|
||||
currentUri: isURIValid(query) ? normalizeURI(query) : null,
|
||||
});
|
||||
search(query);
|
||||
}
|
||||
|
@ -84,52 +77,61 @@ class SearchPage extends React.PureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
handleSearchSubmitted = (keywords) => {
|
||||
handleSearchSubmitted = keywords => {
|
||||
const { search } = this.props;
|
||||
this.setState({ currentUri: (isURIValid(keywords)) ? normalizeURI(keywords) : null });
|
||||
this.setState({ currentUri: isURIValid(keywords) ? normalizeURI(keywords) : null });
|
||||
search(keywords);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isSearching, navigation, query, uris, urisByQuery } = this.props;
|
||||
|
||||
return (
|
||||
<View style={searchStyle.container}>
|
||||
<UriBar value={query}
|
||||
navigation={navigation}
|
||||
onSearchSubmitted={this.handleSearchSubmitted} />
|
||||
{isSearching &&
|
||||
<UriBar value={query} navigation={navigation} onSearchSubmitted={this.handleSearchSubmitted} />
|
||||
{isSearching && (
|
||||
<View style={searchStyle.busyContainer}>
|
||||
<ActivityIndicator size="large" color={Colors.LbryGreen} style={searchStyle.loading} />
|
||||
</View>}
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{!isSearching &&
|
||||
<ScrollView
|
||||
style={searchStyle.scrollContainer}
|
||||
contentContainerStyle={searchStyle.scrollPadding}
|
||||
keyboardShouldPersistTaps={'handled'}>
|
||||
{this.state.currentUri &&
|
||||
<FileListItem
|
||||
key={this.state.currentUri}
|
||||
uri={this.state.currentUri}
|
||||
featuredResult={true}
|
||||
style={searchStyle.featuredResultItem}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, this.state.currentUri)}
|
||||
/>}
|
||||
{(uris && uris.length) ? (
|
||||
uris.map(uri => <FileListItem key={uri}
|
||||
uri={uri}
|
||||
style={searchStyle.resultItem}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, uri)}/>)
|
||||
) : 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>}
|
||||
{!isSearching && (
|
||||
<ScrollView
|
||||
style={searchStyle.scrollContainer}
|
||||
contentContainerStyle={searchStyle.scrollPadding}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
>
|
||||
{this.state.currentUri && (
|
||||
<FileListItem
|
||||
key={this.state.currentUri}
|
||||
uri={this.state.currentUri}
|
||||
featuredResult={true}
|
||||
style={searchStyle.featuredResultItem}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, this.state.currentUri)}
|
||||
/>
|
||||
)}
|
||||
{uris && uris.length
|
||||
? uris.map(uri => (
|
||||
<FileListItem
|
||||
key={uri}
|
||||
uri={uri}
|
||||
style={searchStyle.resultItem}
|
||||
navigation={navigation}
|
||||
onPress={() => navigateToUri(navigation, uri)}
|
||||
/>
|
||||
))
|
||||
: 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} />
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -22,4 +22,7 @@ const perform = dispatch => ({
|
|||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SettingsPage);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(SettingsPage);
|
||||
|
|
|
@ -8,8 +8,8 @@ import settingsStyle from 'styles/settings';
|
|||
|
||||
class SettingsPage extends React.PureComponent {
|
||||
static navigationOptions = {
|
||||
title: 'Settings'
|
||||
}
|
||||
title: 'Settings',
|
||||
};
|
||||
|
||||
didFocusListener;
|
||||
|
||||
|
@ -28,7 +28,7 @@ class SettingsPage extends React.PureComponent {
|
|||
const { pushDrawerStack, setPlayerVisible } = this.props;
|
||||
pushDrawerStack();
|
||||
setPlayerVisible();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.onComponentFocused();
|
||||
|
@ -50,24 +50,29 @@ class SettingsPage extends React.PureComponent {
|
|||
navigation,
|
||||
popDrawerStack,
|
||||
showNsfw,
|
||||
setClientSetting
|
||||
setClientSetting,
|
||||
} = this.props;
|
||||
|
||||
// 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 (
|
||||
<View style={settingsStyle.container}>
|
||||
<PageHeader title={"Settings"}
|
||||
onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
||||
<PageHeader title={'Settings'} onBackPressed={() => navigateBack(navigation, drawerStack, popDrawerStack)} />
|
||||
<ScrollView style={settingsStyle.scrollContainer}>
|
||||
<View style={settingsStyle.row}>
|
||||
<View style={settingsStyle.switchText}>
|
||||
<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 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>
|
||||
|
||||
|
@ -76,22 +81,28 @@ class SettingsPage extends React.PureComponent {
|
|||
<Text style={settingsStyle.label}>Show NSFW content</Text>
|
||||
</View>
|
||||
<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 style={settingsStyle.row}>
|
||||
<View style={settingsStyle.switchText}>
|
||||
<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 style={settingsStyle.switchContainer}>
|
||||
<Switch value={actualKeepDaemonRunning} onValueChange={(value) => {
|
||||
setClientSetting(SETTINGS.KEEP_DAEMON_RUNNING, value);
|
||||
if (NativeModules.DaemonServiceControl) {
|
||||
NativeModules.DaemonServiceControl.setKeepDaemonRunning(value);
|
||||
}
|
||||
}} />
|
||||
<Switch
|
||||
value={actualKeepDaemonRunning}
|
||||
onValueChange={value => {
|
||||
setClientSetting(SETTINGS.KEEP_DAEMON_RUNNING, value);
|
||||
if (NativeModules.DaemonServiceControl) {
|
||||
NativeModules.DaemonServiceControl.setKeepDaemonRunning(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue
Didn't prettier/linter get added?