Splash screen, discover and file Pages (#30)
This commit is contained in:
parent
d6b09dede6
commit
2063e716f5
39 changed files with 100386 additions and 820 deletions
|
@ -12,7 +12,7 @@ install:
|
||||||
- sudo pip install --upgrade cython==0.25.2 pip setuptools
|
- sudo pip install --upgrade cython==0.25.2 pip setuptools
|
||||||
- git clone https://github.com/akinwale/buildozer.git
|
- git clone https://github.com/akinwale/buildozer.git
|
||||||
- cd app
|
- cd app
|
||||||
- npm install --silent --save react@16.0.0 react-native
|
- npm install --silent --save react@16.2.0 react-native@0.52.0
|
||||||
- cd ..
|
- cd ..
|
||||||
- cd buildozer
|
- cd buildozer
|
||||||
- sudo python setup.py install
|
- sudo python setup.py install
|
||||||
|
@ -34,5 +34,5 @@ install:
|
||||||
- mkdir -p ~/.buildozer/android/platform/android-sdk-23/licenses
|
- mkdir -p ~/.buildozer/android/platform/android-sdk-23/licenses
|
||||||
- echo $'\nd56f5187479451eabf01fb78af6dfcb131a6481e' > ~/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license
|
- echo $'\nd56f5187479451eabf01fb78af6dfcb131a6481e' > ~/.buildozer/android/platform/android-sdk-23/licenses/android-sdk-license
|
||||||
script:
|
script:
|
||||||
- buildozer android debug | grep -Fv -e 'working:' -e 'copy' --line-buffered
|
- ./build.sh | grep -Fv -e 'working:' -e 'copy' --line-buffered
|
||||||
- cp bin/*.apk /dev/null
|
- cp bin/*.apk /dev/null
|
|
@ -1,2 +1,2 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
|
react-native bundle --platform android --dev false --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
|
||||||
|
|
7101
app/package-lock.json
generated
7101
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -3,10 +3,21 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node node_modules/react-native/local-cli/cli.js start"
|
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||||
|
"postinstall": "cp ./patch/ReactNativeRenderer-dev.js.patch ./node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js; rm ./node_modules/react-native/local-cli/core/__fixtures__/files/package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^16.0.0",
|
"lbry-redux": "lbryio/lbry-redux#common-components-refactor",
|
||||||
"react-native": "^0.51.0"
|
"react": "16.2.0",
|
||||||
|
"react-native": "0.52.0",
|
||||||
|
"react-native-vector-icons": "^4.5.0",
|
||||||
|
"react-navigation": "^1.0.3",
|
||||||
|
"react-navigation-redux-helpers": "^1.0.1",
|
||||||
|
"react-redux": "^5.0.3",
|
||||||
|
"redux": "^3.6.0",
|
||||||
|
"redux-persist": "^4.8.0",
|
||||||
|
"redux-persist-transform-compress": "^4.2.0",
|
||||||
|
"redux-persist-transform-filter": "0.0.10",
|
||||||
|
"redux-thunk": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13009
app/patch/ReactNativeRenderer-dev.js.patch
Normal file
13009
app/patch/ReactNativeRenderer-dev.js.patch
Normal file
File diff suppressed because it is too large
Load diff
84
app/src/component/AppNavigator.js
Normal file
84
app/src/component/AppNavigator.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import React from 'react';
|
||||||
|
import DiscoverPage from '../page/discover';
|
||||||
|
import FilePage from '../page/file';
|
||||||
|
import SplashScreen from '../page/splash';
|
||||||
|
import { addNavigationHelpers, DrawerNavigator, StackNavigator } from 'react-navigation';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { addListener } from '../utils/redux';
|
||||||
|
import { BackHandler } from 'react-native';
|
||||||
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
import discoverStyle from '../styles/discover';
|
||||||
|
|
||||||
|
const discoverStack = StackNavigator({
|
||||||
|
Discover: {
|
||||||
|
screen: DiscoverPage,
|
||||||
|
navigationOptions: ({ navigation }) => ({
|
||||||
|
title: 'Discover',
|
||||||
|
headerLeft: <Feather name="menu" size={24} style={discoverStyle.drawerHamburger} onPress={() => navigation.navigate('DrawerOpen')} />
|
||||||
|
})
|
||||||
|
},
|
||||||
|
File: {
|
||||||
|
screen: FilePage,
|
||||||
|
navigationOptions: {
|
||||||
|
header: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'screen',
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawer = DrawerNavigator({
|
||||||
|
Discover: { screen: discoverStack },
|
||||||
|
}, {
|
||||||
|
drawerWidth: 300,
|
||||||
|
headerMode: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AppNavigator = new StackNavigator({
|
||||||
|
Splash: {
|
||||||
|
screen: SplashScreen
|
||||||
|
},
|
||||||
|
Main: {
|
||||||
|
screen: drawer
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
headerMode: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
class AppWithNavigationState extends React.Component {
|
||||||
|
componentWillMount() {
|
||||||
|
BackHandler.addEventListener('hardwareBackPress', function() {
|
||||||
|
const { dispatch, navigation, nav } = this.props;
|
||||||
|
if (nav.routes.length === 2 && nav.routes[1].routeName === 'Main') {
|
||||||
|
if (nav.routes[1].routes[0].routes[0].index > 0) {
|
||||||
|
dispatch({ type: 'Navigation/BACK' });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
BackHandler.removeEventListener('hardwareBackPress');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { dispatch, nav } = this.props;
|
||||||
|
return (
|
||||||
|
<AppNavigator
|
||||||
|
navigation={addNavigationHelpers({
|
||||||
|
dispatch,
|
||||||
|
state: nav,
|
||||||
|
addListener,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
nav: state.nav,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(AppWithNavigationState);
|
7
app/src/component/featuredCategory/index.js
Normal file
7
app/src/component/featuredCategory/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import FeaturedCategory from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(FeaturedCategory);
|
27
app/src/component/featuredCategory/view.js
Normal file
27
app/src/component/featuredCategory/view.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { normalizeURI } from 'lbry-redux';
|
||||||
|
import FileItem from '../fileItem';
|
||||||
|
import discoverStyle from '../../styles/discover';
|
||||||
|
|
||||||
|
class FeaturedCategory extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { category, names, categoryLink, navigation } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Text style={discoverStyle.categoryName}>{category}</Text>
|
||||||
|
{names &&
|
||||||
|
names.map(name => (
|
||||||
|
<FileItem
|
||||||
|
style={discoverStyle.fileItem}
|
||||||
|
key={name}
|
||||||
|
uri={normalizeURI(name)}
|
||||||
|
navigation={navigation} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FeaturedCategory;
|
25
app/src/component/fileItem/index.js
Normal file
25
app/src/component/fileItem/index.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doResolveUri,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectMetadataForUri,
|
||||||
|
makeSelectFileInfoForUri,
|
||||||
|
makeSelectIsUriResolving,
|
||||||
|
selectRewardContentClaimIds
|
||||||
|
} from 'lbry-redux';
|
||||||
|
/*import { selectShowNsfw } from 'redux/selectors/settings';*/
|
||||||
|
import FileItem from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
|
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||||
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
|
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(FileItem);
|
71
app/src/component/fileItem/view.js
Normal file
71
app/src/component/fileItem/view.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { normalizeURI } from 'lbry-redux';
|
||||||
|
import { NavigationActions } from 'react-navigation';
|
||||||
|
import { Text, View, TouchableOpacity } from 'react-native';
|
||||||
|
import FileItemMedia from '../fileItemMedia';
|
||||||
|
import FilePrice from '../filePrice';
|
||||||
|
import discoverStyle from '../../styles/discover';
|
||||||
|
|
||||||
|
class FileItem extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.resolve(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.resolve(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(props) {
|
||||||
|
const { isResolvingUri, resolveUri, claim, uri } = props;
|
||||||
|
|
||||||
|
if (!isResolvingUri && claim === undefined && uri) {
|
||||||
|
resolveUri(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
claim,
|
||||||
|
fileInfo,
|
||||||
|
metadata,
|
||||||
|
isResolvingUri,
|
||||||
|
rewardedContentClaimIds,
|
||||||
|
style
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const uri = normalizeURI(this.props.uri);
|
||||||
|
const title = metadata && metadata.title ? metadata.title : uri;
|
||||||
|
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
||||||
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
const channelName = claim ? claim.channel_name : null;
|
||||||
|
|
||||||
|
let description = '';
|
||||||
|
if (isResolvingUri && !claim) {
|
||||||
|
description = 'Loading...';
|
||||||
|
} else if (metadata && metadata.description) {
|
||||||
|
description = metadata.description;
|
||||||
|
} else if (claim === null) {
|
||||||
|
description = 'This address contains no content.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={style} onPress={() => {
|
||||||
|
this.props.navigation.navigate('File', { uri: uri });
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
<FileItemMedia title={title} thumbnail={thumbnail} />
|
||||||
|
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
|
||||||
|
<Text style={discoverStyle.fileItemName}>{title}</Text>
|
||||||
|
{channelName &&
|
||||||
|
<Text style={discoverStyle.channelName}>{channelName}</Text>}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileItem;
|
7
app/src/component/fileItemMedia/index.js
Normal file
7
app/src/component/fileItemMedia/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import FileItemMedia from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(FileItemMedia);
|
56
app/src/component/fileItemMedia/view.js
Normal file
56
app/src/component/fileItemMedia/view.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, Image, View } from 'react-native';
|
||||||
|
import fileItemMediaStyle from '../../styles/fileItemMedia';
|
||||||
|
|
||||||
|
class FileItemMedia extends React.PureComponent {
|
||||||
|
static AUTO_THUMB_STYLES = [
|
||||||
|
fileItemMediaStyle.autothumbPurple,
|
||||||
|
fileItemMediaStyle.autothumbRed,
|
||||||
|
fileItemMediaStyle.autothumbPink,
|
||||||
|
fileItemMediaStyle.autothumbIndigo,
|
||||||
|
fileItemMediaStyle.autothumbBlue,
|
||||||
|
fileItemMediaStyle.autothumbLightBlue,
|
||||||
|
fileItemMediaStyle.autothumbCyan,
|
||||||
|
fileItemMediaStyle.autothumbTeal,
|
||||||
|
fileItemMediaStyle.autothumbGreen,
|
||||||
|
fileItemMediaStyle.autothumbYellow,
|
||||||
|
fileItemMediaStyle.autothumbOrange,
|
||||||
|
];
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
autoThumbStyle:
|
||||||
|
FileItemMedia.AUTO_THUMB_STYLES[
|
||||||
|
Math.floor(Math.random() * FileItemMedia.AUTO_THUMB_STYLES.length)
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let style = this.props.style;
|
||||||
|
const { title, thumbnail } = this.props;
|
||||||
|
const atStyle = this.state.autoThumbStyle;
|
||||||
|
|
||||||
|
if (thumbnail && ((typeof thumbnail) === 'string')) {
|
||||||
|
if (style == null) {
|
||||||
|
style = fileItemMediaStyle.thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image source={{uri: thumbnail}} resizeMode="cover" style={style} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[fileItemMediaStyle.autothumb, atStyle]}>
|
||||||
|
<Text style={fileItemMediaStyle.autothumbText}>{title &&
|
||||||
|
title
|
||||||
|
.replace(/\s+/g, '')
|
||||||
|
.substring(0, Math.min(title.replace(' ', '').length, 5))
|
||||||
|
.toUpperCase()}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileItemMedia;
|
20
app/src/component/filePrice/index.js
Normal file
20
app/src/component/filePrice/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doFetchCostInfoForUri,
|
||||||
|
makeSelectCostInfoForUri,
|
||||||
|
makeSelectFetchingCostInfoForUri,
|
||||||
|
makeSelectClaimForUri
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import FilePrice from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||||
|
fetching: makeSelectFetchingCostInfoForUri(props.uri)(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(FilePrice);
|
120
app/src/component/filePrice/view.js
Normal file
120
app/src/component/filePrice/view.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { formatCredits, formatFullPrice } from 'lbry-redux';
|
||||||
|
|
||||||
|
class CreditAmount extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
amount: PropTypes.number.isRequired,
|
||||||
|
precision: PropTypes.number,
|
||||||
|
isEstimate: PropTypes.bool,
|
||||||
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||||
|
showFree: PropTypes.bool,
|
||||||
|
showFullPrice: PropTypes.bool,
|
||||||
|
showPlus: PropTypes.bool,
|
||||||
|
look: PropTypes.oneOf(['indicator', 'plain', 'fee']),
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
precision: 2,
|
||||||
|
label: true,
|
||||||
|
showFree: false,
|
||||||
|
look: 'indicator',
|
||||||
|
showFullPrice: false,
|
||||||
|
showPlus: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const minimumRenderableAmount = Math.pow(10, -1 * this.props.precision);
|
||||||
|
const { amount, precision, showFullPrice, style } = this.props;
|
||||||
|
|
||||||
|
let formattedAmount;
|
||||||
|
const fullPrice = formatFullPrice(amount, 2);
|
||||||
|
|
||||||
|
if (showFullPrice) {
|
||||||
|
formattedAmount = fullPrice;
|
||||||
|
} else {
|
||||||
|
formattedAmount =
|
||||||
|
amount > 0 && amount < minimumRenderableAmount
|
||||||
|
? `<${minimumRenderableAmount}`
|
||||||
|
: formatCredits(amount, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
let amountText;
|
||||||
|
if (this.props.showFree && parseFloat(this.props.amount) === 0) {
|
||||||
|
amountText = 'FREE';
|
||||||
|
} else {
|
||||||
|
if (this.props.label) {
|
||||||
|
const label =
|
||||||
|
typeof this.props.label === 'string'
|
||||||
|
? this.props.label
|
||||||
|
: parseFloat(amount) == 1 ? 'credit' : 'credits';
|
||||||
|
|
||||||
|
amountText = `${formattedAmount} ${label}`;
|
||||||
|
} else {
|
||||||
|
amountText = formattedAmount;
|
||||||
|
}
|
||||||
|
if (this.props.showPlus && amount > 0) {
|
||||||
|
amountText = `+${amountText}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*{this.props.isEstimate ? (
|
||||||
|
<span
|
||||||
|
className="credit-amount__estimate"
|
||||||
|
title={__('This is an estimate and does not include data fees')}
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
) : null}*/
|
||||||
|
return (
|
||||||
|
<Text style={style}>{amountText}</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilePrice extends React.PureComponent {
|
||||||
|
componentWillMount() {
|
||||||
|
this.fetchCost(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.fetchCost(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCost(props) {
|
||||||
|
const { costInfo, fetchCostInfo, uri, fetching, claim } = props;
|
||||||
|
|
||||||
|
if (costInfo === undefined && !fetching && claim) {
|
||||||
|
fetchCostInfo(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { costInfo, look = 'indicator', showFullPrice = false, style, textStyle } = this.props;
|
||||||
|
|
||||||
|
const isEstimate = costInfo ? !costInfo.includesData : null;
|
||||||
|
|
||||||
|
if (!costInfo) {
|
||||||
|
return (
|
||||||
|
<View style={style}>
|
||||||
|
<Text style={textStyle}>???</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style}>
|
||||||
|
<CreditAmount
|
||||||
|
style={textStyle}
|
||||||
|
label={false}
|
||||||
|
amount={costInfo.cost}
|
||||||
|
isEstimate={isEstimate}
|
||||||
|
showFree
|
||||||
|
showFullPrice={showFullPrice}>???</CreditAmount>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilePrice;
|
116
app/src/index.js
Normal file
116
app/src/index.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider, connect } from 'react-redux';
|
||||||
|
import DiscoverPage from './page/discover';
|
||||||
|
import { AppRegistry, StyleSheet, Text, View, AsyncStorage } from 'react-native';
|
||||||
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
|
import {
|
||||||
|
StackNavigator, addNavigationHelpers
|
||||||
|
} from 'react-navigation';
|
||||||
|
import { AppNavigator } from './component/AppNavigator';
|
||||||
|
import AppWithNavigationState from './component/AppNavigator';
|
||||||
|
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||||
|
import createCompressor from 'redux-persist-transform-compress';
|
||||||
|
import createFilter from 'redux-persist-transform-filter';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import {
|
||||||
|
Lbry,
|
||||||
|
claimsReducer,
|
||||||
|
costInfoReducer,
|
||||||
|
fileInfoReducer,
|
||||||
|
searchReducer,
|
||||||
|
walletReducer
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { reactNavigationMiddleware } from './utils/redux';
|
||||||
|
|
||||||
|
function isFunction(object) {
|
||||||
|
return typeof object === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotFunction(object) {
|
||||||
|
return !isFunction(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBulkThunkMiddleware() {
|
||||||
|
return ({ dispatch, getState }) => next => action => {
|
||||||
|
if (action.type === 'BATCH_ACTIONS') {
|
||||||
|
action.actions.filter(isFunction).map(actionFn => actionFn(dispatch, getState));
|
||||||
|
}
|
||||||
|
return next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableBatching(reducer) {
|
||||||
|
return function batchingReducer(state, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'BATCH_ACTIONS':
|
||||||
|
return action.actions.filter(isNotFunction).reduce(batchingReducer, state);
|
||||||
|
default:
|
||||||
|
return reducer(state, action);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = AppNavigator.router;
|
||||||
|
const navAction = router.getActionForPathAndParams('Splash');
|
||||||
|
const initialNavState = router.getStateForAction(navAction);
|
||||||
|
const navigatorReducer = (state = initialNavState, action) => {
|
||||||
|
const nextState = AppNavigator.router.getStateForAction(action, state);
|
||||||
|
return nextState || state;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducers = combineReducers({
|
||||||
|
claims: claimsReducer,
|
||||||
|
costInfo: costInfoReducer,
|
||||||
|
fileInfo: fileInfoReducer,
|
||||||
|
search: searchReducer,
|
||||||
|
wallet: walletReducer,
|
||||||
|
nav: navigatorReducer
|
||||||
|
});
|
||||||
|
|
||||||
|
const bulkThunk = createBulkThunkMiddleware();
|
||||||
|
const middleware = [thunk, bulkThunk, reactNavigationMiddleware];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
const composeEnhancers = compose;
|
||||||
|
|
||||||
|
const store = createStore(
|
||||||
|
enableBatching(reducers),
|
||||||
|
{}, // initial state,
|
||||||
|
composeEnhancers(
|
||||||
|
autoRehydrate(),
|
||||||
|
applyMiddleware(...middleware)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const compressor = createCompressor();
|
||||||
|
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||||
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||||
|
|
||||||
|
const persistOptions = {
|
||||||
|
whitelist: ['claims', 'subscriptions'],
|
||||||
|
// Order is important. Needs to be compressed last or other transforms can't
|
||||||
|
// read the data
|
||||||
|
transforms: [saveClaimsFilter, subscriptionsFilter, compressor],
|
||||||
|
debounce: 10000,
|
||||||
|
storage: AsyncStorage
|
||||||
|
};
|
||||||
|
|
||||||
|
persistStore(store, persistOptions, err => {
|
||||||
|
if (err) {
|
||||||
|
console.log('Unable to load saved SETTINGS');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class LBRYApp extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Provider store={store}>
|
||||||
|
<AppWithNavigationState />
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppRegistry.registerComponent('LBRYApp', () => LBRYApp);
|
||||||
|
|
||||||
|
export default LBRYApp;
|
14
app/src/page/discover/index.js
Normal file
14
app/src/page/discover/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doFetchFeaturedUris, selectFeaturedUris, selectFetchingFeaturedUris } from 'lbry-redux';
|
||||||
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
featuredUris: selectFeaturedUris(state),
|
||||||
|
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(DiscoverPage);
|
44
app/src/page/discover/view.js
Normal file
44
app/src/page/discover/view.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react';
|
||||||
|
import FeaturedCategory from '../../component/featuredCategory';
|
||||||
|
import NavigationActions from 'react-navigation';
|
||||||
|
import { Text, View, ScrollView } from 'react-native';
|
||||||
|
import discoverStyle from '../../styles/discover';
|
||||||
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
|
||||||
|
class DiscoverPage extends React.PureComponent {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.fetchFeaturedUris();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { featuredUris, fetchingFeaturedUris } = this.props;
|
||||||
|
const hasContent = typeof featuredUris === 'object' && Object.keys(featuredUris).length,
|
||||||
|
failedToLoad = !fetchingFeaturedUris && !hasContent;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={discoverStyle.container}>
|
||||||
|
{!hasContent && fetchingFeaturedUris && <Text style={discoverStyle.title}>Fetching content...</Text>}
|
||||||
|
{hasContent &&
|
||||||
|
<ScrollView style={discoverStyle.scrollContainer}>
|
||||||
|
{hasContent &&
|
||||||
|
Object.keys(featuredUris).map(
|
||||||
|
category =>
|
||||||
|
featuredUris[category].length ? (
|
||||||
|
<FeaturedCategory
|
||||||
|
key={category}
|
||||||
|
category={category}
|
||||||
|
names={featuredUris[category]}
|
||||||
|
navigation={this.props.navigation}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscoverPage;
|
34
app/src/page/file/index.js
Normal file
34
app/src/page/file/index.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doFetchFileInfo,
|
||||||
|
makeSelectFileInfoForUri,
|
||||||
|
doFetchCostInfoForUri,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectContentTypeForUri,
|
||||||
|
makeSelectMetadataForUri,
|
||||||
|
selectRewardContentClaimIds,
|
||||||
|
makeSelectCostInfoForUri
|
||||||
|
} from 'lbry-redux';
|
||||||
|
//import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
|
import FilePage from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const selectProps = { uri: props.navigation.state.params.uri };
|
||||||
|
return {
|
||||||
|
claim: makeSelectClaimForUri(selectProps.uri)(state),
|
||||||
|
contentType: makeSelectContentTypeForUri(selectProps.uri)(state),
|
||||||
|
costInfo: makeSelectCostInfoForUri(selectProps.uri)(state),
|
||||||
|
metadata: makeSelectMetadataForUri(selectProps.uri)(state),
|
||||||
|
//obscureNsfw: !selectShowNsfw(state),
|
||||||
|
//tab: makeSelectCurrentParam('tab')(state),
|
||||||
|
fileInfo: makeSelectFileInfoForUri(selectProps.uri)(state),
|
||||||
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(FilePage);
|
79
app/src/page/file/view.js
Normal file
79
app/src/page/file/view.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View, ScrollView } from 'react-native';
|
||||||
|
import filePageStyle from '../../styles/filePage';
|
||||||
|
import FileItemMedia from '../../component/fileItemMedia';
|
||||||
|
|
||||||
|
class FilePage extends React.PureComponent {
|
||||||
|
static navigationOptions = {
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchFileInfo(this.props);
|
||||||
|
this.fetchCostInfo(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.fetchFileInfo(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFileInfo(props) {
|
||||||
|
if (props.fileInfo === undefined) {
|
||||||
|
props.fetchFileInfo(props.navigation.state.params.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCostInfo(props) {
|
||||||
|
if (props.costInfo === undefined) {
|
||||||
|
props.fetchCostInfo(props.navigation.state.params.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
claim,
|
||||||
|
fileInfo,
|
||||||
|
metadata,
|
||||||
|
contentType,
|
||||||
|
tab,
|
||||||
|
uri,
|
||||||
|
rewardedContentClaimIds,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!claim || !metadata) {
|
||||||
|
return (
|
||||||
|
<View style={filePageStyle.container}>
|
||||||
|
<Text style={filePageStyle.emptyClaimText}>Empty claim or metadata info.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = metadata.title;
|
||||||
|
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
const description = metadata.description ? metadata.description : null;
|
||||||
|
//const mediaType = lbry.getMediaType(contentType);
|
||||||
|
//const player = require('render-media');
|
||||||
|
//const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
/*const isPlayable =
|
||||||
|
Object.values(player.mime).indexOf(contentType) !== -1 || mediaType === 'audio';*/
|
||||||
|
const { height, channel_name: channelName, value } = claim;
|
||||||
|
const channelClaimId =
|
||||||
|
value && value.publisherSignature && value.publisherSignature.certificateId;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={filePageStyle.pageContainer}>
|
||||||
|
<View style={filePageStyle.mediaContainer}>
|
||||||
|
<FileItemMedia style={filePageStyle.thumbnail} title={title} thumbnail={metadata.thumbnail} />
|
||||||
|
</View>
|
||||||
|
<ScrollView style={filePageStyle.scrollContainer}>
|
||||||
|
<Text style={filePageStyle.title}>{title}</Text>
|
||||||
|
{channelName && <Text style={filePageStyle.channelName}>{channelName}</Text>}
|
||||||
|
{description && <Text style={filePageStyle.description}>{description}</Text>}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilePage;
|
7
app/src/page/splash/index.js
Normal file
7
app/src/page/splash/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import SplashScreen from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(SplashScreen);
|
97
app/src/page/splash/view.js
Normal file
97
app/src/page/splash/view.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry } from 'lbry-redux';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import splashStyle from '../../styles/splash';
|
||||||
|
|
||||||
|
class SplashScreen extends React.PureComponent {
|
||||||
|
static navigationOptions = {
|
||||||
|
title: 'Splash'
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
details: 'Starting daemon',
|
||||||
|
message: 'Connecting',
|
||||||
|
isRunning: false,
|
||||||
|
isLagging: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus() {
|
||||||
|
Lbry.status().then(status => {
|
||||||
|
this._updateStatusCallback(status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateStatusCallback(status) {
|
||||||
|
const startupStatus = status.startup_status;
|
||||||
|
if (startupStatus.code == 'started') {
|
||||||
|
// Wait until we are able to resolve a name before declaring
|
||||||
|
// that we are done.
|
||||||
|
// TODO: This is a hack, and the logic should live in the daemon
|
||||||
|
// to give us a better sense of when we are actually started
|
||||||
|
this.setState({
|
||||||
|
message: 'Testing Network',
|
||||||
|
details: 'Waiting for name resolution',
|
||||||
|
isLagging: false,
|
||||||
|
isRunning: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||||
|
// Leave the splash screen
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.navigate('Main');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status.blockchain_status && status.blockchain_status.blocks_behind > 0) {
|
||||||
|
const behind = status.blockchain_status.blocks_behind;
|
||||||
|
const behindText = behind + ' block' + (behind == 1 ? '' : 's') + ' behind';
|
||||||
|
this.setState({
|
||||||
|
message: 'Blockchain Sync',
|
||||||
|
details: behindText,
|
||||||
|
isLagging: startupStatus.is_lagging,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
message: 'Network Loading',
|
||||||
|
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
|
||||||
|
isLagging: startupStatus.is_lagging,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateStatus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Lbry
|
||||||
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
this.updateStatus();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.setState({
|
||||||
|
isLagging: true,
|
||||||
|
message: 'Connection Failure',
|
||||||
|
details:
|
||||||
|
'We could not establish a connection to the daemon. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { message, details, isLagging, isRunning } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={splashStyle.container}>
|
||||||
|
<Text style={splashStyle.title}>Lbry.</Text>
|
||||||
|
<Text style={splashStyle.message}>{message}</Text>
|
||||||
|
<Text style={splashStyle.details}>{details}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SplashScreen;
|
60
app/src/styles/discover.js
Normal file
60
app/src/styles/discover.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const discoverStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
scrollContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 10,
|
||||||
|
},
|
||||||
|
categoryName: {
|
||||||
|
fontSize: 20,
|
||||||
|
marginLeft: 24,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
color: '#40b89a'
|
||||||
|
},
|
||||||
|
fileItem: {
|
||||||
|
marginLeft: 24,
|
||||||
|
marginRight: 24,
|
||||||
|
marginBottom: 48
|
||||||
|
},
|
||||||
|
fileItemName: {
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
channelName: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 4,
|
||||||
|
color: '#c0c0c0',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
filePriceContainer: {
|
||||||
|
backgroundColor: '#61fcd8',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 16,
|
||||||
|
top: 16,
|
||||||
|
width: 56,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
filePriceText: {
|
||||||
|
fontSize: 12,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#0c604b',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
drawerHamburger: {
|
||||||
|
marginLeft: 8
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default discoverStyle;
|
57
app/src/styles/fileItemMedia.js
Normal file
57
app/src/styles/fileItemMedia.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { StyleSheet, Dimensions } from 'react-native';
|
||||||
|
|
||||||
|
const screenDimension = Dimensions.get('window');
|
||||||
|
const width = screenDimension.width - 48; // screen width minus combined left and right margins
|
||||||
|
|
||||||
|
const fileItemMediaStyle = StyleSheet.create({
|
||||||
|
autothumb: {
|
||||||
|
width: width,
|
||||||
|
height: 180,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
autothumbText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: 40
|
||||||
|
},
|
||||||
|
autothumbPurple: {
|
||||||
|
backgroundColor: '#9c27b0'
|
||||||
|
},
|
||||||
|
autothumbRed: {
|
||||||
|
backgroundColor: '#e53935'
|
||||||
|
},
|
||||||
|
autothumbPink: {
|
||||||
|
backgroundColor: '#e91e63'
|
||||||
|
},
|
||||||
|
autothumbIndigo: {
|
||||||
|
backgroundColor: '#3f51b5'
|
||||||
|
},
|
||||||
|
autothumbBlue: {
|
||||||
|
backgroundColor: '#2196f3'
|
||||||
|
},
|
||||||
|
autothumbLightBlue: {
|
||||||
|
backgroundColor: '#039be5'
|
||||||
|
},
|
||||||
|
autothumbCyan: {
|
||||||
|
backgroundColor: '#00acc1'
|
||||||
|
},
|
||||||
|
autothumbTeal: {
|
||||||
|
backgroundColor: '#009688'
|
||||||
|
},
|
||||||
|
autothumbGreen: {
|
||||||
|
backgroundColor: '#43a047'
|
||||||
|
},
|
||||||
|
autothumbYellow: {
|
||||||
|
backgroundColor: '#ffeb3b'
|
||||||
|
},
|
||||||
|
autothumbOrange: {
|
||||||
|
backgroundColor: '#ffa726'
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
width: width,
|
||||||
|
height: 180,
|
||||||
|
shadowColor: 'transparent'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default fileItemMediaStyle;
|
55
app/src/styles/filePage.js
Normal file
55
app/src/styles/filePage.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { StyleSheet, Dimensions } from 'react-native';
|
||||||
|
|
||||||
|
const screenDimension = Dimensions.get('window');
|
||||||
|
const screenWidth = screenDimension.width;
|
||||||
|
|
||||||
|
const filePageStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
pageContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
mediaContainer: {
|
||||||
|
backgroundColor: '#000000'
|
||||||
|
},
|
||||||
|
emptyClaimText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 20,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16
|
||||||
|
},
|
||||||
|
scrollContainer: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginTop: 20,
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
marginBottom: 12
|
||||||
|
},
|
||||||
|
channelName: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
color: '#9b9b9b'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
color: '#999999'
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
width: screenWidth,
|
||||||
|
height: 200
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default filePageStyle;
|
34
app/src/styles/splash.js
Normal file
34
app/src/styles/splash.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
const splashStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#40b89a'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 64,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 48,
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
color: '#ffffff',
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#ffffff',
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
marginBottom: 4,
|
||||||
|
textAlign: 'center'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default splashStyle;
|
15
app/src/utils/redux.js
Normal file
15
app/src/utils/redux.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {
|
||||||
|
createReactNavigationReduxMiddleware,
|
||||||
|
createReduxBoundAddListener,
|
||||||
|
} from 'react-navigation-redux-helpers';
|
||||||
|
|
||||||
|
const reactNavigationMiddleware = createReactNavigationReduxMiddleware(
|
||||||
|
"root",
|
||||||
|
state => state.nav,
|
||||||
|
);
|
||||||
|
const addListener = createReduxBoundAddListener("root");
|
||||||
|
|
||||||
|
export {
|
||||||
|
reactNavigationMiddleware,
|
||||||
|
addListener,
|
||||||
|
};
|
2
build.sh
2
build.sh
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd app
|
cd app
|
||||||
react-native bundle --platform android --dev false --entry-file index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
|
react-native bundle --platform android --dev false --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
|
||||||
cd ..
|
cd ..
|
||||||
buildozer android debug
|
buildozer android debug
|
||||||
|
|
6
deploy.sh
Executable file
6
deploy.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cd app
|
||||||
|
react-native bundle --platform android --dev true --entry-file src/index.js --bundle-output ../src/main/assets/index.android.bundle --assets-dest ../src/main/res/
|
||||||
|
cd ..
|
||||||
|
buildozer android debug deploy
|
||||||
|
|
|
@ -67,6 +67,8 @@
|
||||||
android:screenOrientation="{{ args.orientation }}"
|
android:screenOrientation="{{ args.orientation }}"
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
|
||||||
<activity android:name="io.lbry.lbrynet.MainActivity"
|
<activity android:name="io.lbry.lbrynet.MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
|
BIN
src/main/assets/fonts/Feather.ttf
Executable file
BIN
src/main/assets/fonts/Feather.ttf
Executable file
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
fzv‚όκ9©φ¥yFΓ“Ο}‘ά³θ
|
JR['jÎÉŕ™©˛uaZ
|
|
@ -1,7 +1,13 @@
|
||||||
package io.lbry.lbrynet;
|
package io.lbry.lbrynet;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
import com.facebook.react.common.LifecycleState;
|
import com.facebook.react.common.LifecycleState;
|
||||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||||
|
@ -10,13 +16,35 @@ import com.facebook.react.ReactInstanceManager;
|
||||||
import com.facebook.react.shell.MainReactPackage;
|
import com.facebook.react.shell.MainReactPackage;
|
||||||
|
|
||||||
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
|
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
|
||||||
|
private static final int OVERLAY_PERMISSION_REQ_CODE = 101;
|
||||||
|
|
||||||
private ReactRootView mReactRootView;
|
private ReactRootView mReactRootView;
|
||||||
private ReactInstanceManager mReactInstanceManager;
|
private ReactInstanceManager mReactInstanceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag which indicates whether or not the service is running. Will be updated in the
|
||||||
|
* onResume method.
|
||||||
|
*/
|
||||||
|
private boolean serviceRunning;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (!Settings.canDrawOverlays(this)) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||||
|
Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Start the daemon service if it is not started
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
if (!serviceRunning) {
|
||||||
|
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
|
||||||
|
}
|
||||||
|
|
||||||
mReactRootView = new ReactRootView(this);
|
mReactRootView = new ReactRootView(this);
|
||||||
mReactInstanceManager = ReactInstanceManager.builder()
|
mReactInstanceManager = ReactInstanceManager.builder()
|
||||||
.setApplication(getApplication())
|
.setApplication(getApplication())
|
||||||
|
@ -31,6 +59,17 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
setContentView(mReactRootView);
|
setContentView(mReactRootView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (!Settings.canDrawOverlays(this)) {
|
||||||
|
// SYSTEM_ALERT_WINDOW permission not granted...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invokeDefaultOnBackPressed() {
|
public void invokeDefaultOnBackPressed() {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
|
@ -49,6 +88,11 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||||
|
if (!serviceRunning) {
|
||||||
|
ServiceHelper.start(this, "", LbrynetService.class, "lbrynetservice");
|
||||||
|
}
|
||||||
|
|
||||||
if (mReactInstanceManager != null) {
|
if (mReactInstanceManager != null) {
|
||||||
mReactInstanceManager.onHostResume(this, this);
|
mReactInstanceManager.onHostResume(this, this);
|
||||||
}
|
}
|
||||||
|
@ -71,5 +115,15 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||||
|
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||||
|
if (serviceClass.getName().equals(serviceInfo.service.getClassName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 134 B |
Binary file not shown.
After Width: | Height: | Size: 100 B |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 134 B |
Binary file not shown.
After Width: | Height: | Size: 167 B |
Binary file not shown.
After Width: | Height: | Size: 207 B |
Loading…
Reference in a new issue