Splash screen, discover and file Pages (#30)

This commit is contained in:
akinwale 2018-03-11 16:32:13 +01:00 committed by GitHub
parent d6b09dede6
commit 2063e716f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 100386 additions and 820 deletions

View file

@ -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

View file

@ -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/

7095
app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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"
} }
} }

File diff suppressed because it is too large Load diff

View 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);

View 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);

View 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;

View 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);

View 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;

View 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);

View 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;

View 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);

View 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
View 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;

View 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);

View 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;

View 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
View 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;

View 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);

View 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;

View 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;

View 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;

View 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
View 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
View 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,
};

View file

@ -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
View 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

View file

@ -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

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
fzvόκ9©φ¥yFΓ“Ο}‘ά³θ JR['jÎÉੲua Z

View file

@ -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