Publishing (#577)
* create gallery module for retrieving media from device * gallery and ui flow for publishing * publishing. add channel selector component. * enable record and take photo buttons * create thumbnails for camera media * upload thumbnails. check publish success status. * update to sdk 0.38.0. add tags / tag selection to publish.
This commit is contained in:
parent
60836ec5ec
commit
8459d10dc7
35 changed files with 2252 additions and 22 deletions
|
@ -25,7 +25,7 @@ build apk:
|
||||||
- cp -f $CI_PROJECT_DIR/scripts/mangled-glibc-syscalls.h ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21/arch-arm/usr/include/crystax/bionic/libc/include/sys/mangled-glibc-syscalls.h
|
- cp -f $CI_PROJECT_DIR/scripts/mangled-glibc-syscalls.h ~/.buildozer/android/crystax-ndk-10.3.2/platforms/android-21/arch-arm/usr/include/crystax/bionic/libc/include/sys/mangled-glibc-syscalls.h
|
||||||
- rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz
|
- rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz
|
||||||
- git secret reveal
|
- git secret reveal
|
||||||
- mv buildozer.spec.travis buildozer.spec
|
- mv buildozer.spec.ci buildozer.spec
|
||||||
- "./release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
|
- "./release.sh | grep -Fv -e 'working:' -e 'copy' -e 'Compiling' --line-buffered"
|
||||||
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk /dev/null
|
- cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk /dev/null
|
||||||
|
|
||||||
|
|
44
app/package-lock.json
generated
44
app/package-lock.json
generated
|
@ -4881,6 +4881,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
|
||||||
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
|
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
|
||||||
},
|
},
|
||||||
|
"gfycat-style-urls": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gfycat-style-urls/-/gfycat-style-urls-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-HirQ+dsQWChjnfwZXB07ytzh3eZQFVlOdO2ML1YvpHBOXplumtzGIAejVu91wmj4Cw7t4760M2fKtgI+NAi/2w=="
|
||||||
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.4",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||||
|
@ -5553,8 +5558,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lbry-redux": {
|
"lbry-redux": {
|
||||||
"version": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
|
"version": "github:lbryio/lbry-redux#9a676ee311d573b84d11f402d918aeee77be76e1",
|
||||||
"from": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
|
"from": "github:lbryio/lbry-redux",
|
||||||
"requires": {
|
"requires": {
|
||||||
"proxy-polyfill": "0.1.6",
|
"proxy-polyfill": "0.1.6",
|
||||||
"reselect": "^3.0.0",
|
"reselect": "^3.0.0",
|
||||||
|
@ -7617,6 +7622,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-camera": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-ay8te4nvL5mGzRjb2QMTOyJX+JfaIW/9oFjFVIkXOB9DzFipfeVTPMdwNx9GMpdmQ0muSXkuF16pa7K/1QLHlQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-country-picker-modal": {
|
"react-native-country-picker-modal": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-country-picker-modal/-/react-native-country-picker-modal-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-country-picker-modal/-/react-native-country-picker-modal-0.6.2.tgz",
|
||||||
|
@ -7666,6 +7679,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-document-picker": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-bHMyAOzFl+II0ZdfzobKsZKvTErmXfmQGalpxpGbeN8+/uhfhUcdp4WuIMecZhFyX6rbj3h3XXLdA12hVlGgmw=="
|
||||||
|
},
|
||||||
"react-native-exception-handler": {
|
"react-native-exception-handler": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-exception-handler/-/react-native-exception-handler-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-exception-handler/-/react-native-exception-handler-2.9.0.tgz",
|
||||||
|
@ -7676,6 +7694,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.4.2.tgz",
|
||||||
"integrity": "sha512-S4E96Lwmx6z6QD3MaAuP7cNcXRLfgEUYU2GB694TbGEoOjk/FO1OnfbxfFp0vUs/klr4HJwACcwihPPxrFTt8w=="
|
"integrity": "sha512-S4E96Lwmx6z6QD3MaAuP7cNcXRLfgEUYU2GB694TbGEoOjk/FO1OnfbxfFp0vUs/klr4HJwACcwihPPxrFTt8w=="
|
||||||
},
|
},
|
||||||
|
"react-native-fs": {
|
||||||
|
"version": "2.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.13.3.tgz",
|
||||||
|
"integrity": "sha512-B62LSSAEYQGItg7KVTzTVVCxezOYFBYp4DMVFbdoZUd1mZVFdqR2sy1HY1mye1VI/Lf3IbxSyZEQ0GmrrdwLjg==",
|
||||||
|
"requires": {
|
||||||
|
"base-64": "^0.1.0",
|
||||||
|
"utf8": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-gesture-handler": {
|
"react-native-gesture-handler": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.2.1.tgz",
|
||||||
|
@ -7738,6 +7765,14 @@
|
||||||
"prop-types": "^15.6.2"
|
"prop-types": "^15.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-super-grid": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-super-grid/-/react-native-super-grid-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-aoK71FGP5sFcLujuODYkAqyFDAZZRpvTeEwwaoXsc0JENhExEG7rGg65T5ELqyykiDOLBihuCLKasK5gLb0WtQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-native-tab-view": {
|
"react-native-tab-view": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-1.4.1.tgz",
|
||||||
|
@ -9534,6 +9569,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
||||||
},
|
},
|
||||||
|
"utf8": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base-64": "^0.1.0",
|
"base-64": "^0.1.0",
|
||||||
"@expo/vector-icons": "^8.1.0",
|
"@expo/vector-icons": "^8.1.0",
|
||||||
"lbry-redux": "lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
|
"gfycat-style-urls": "^1.0.3",
|
||||||
|
"lbry-redux": "lbryio/lbry-redux",
|
||||||
"lbryinc": "lbryio/lbryinc",
|
"lbryinc": "lbryio/lbryinc",
|
||||||
"lodash": ">=4.17.11",
|
"lodash": ">=4.17.11",
|
||||||
"merge": ">=1.2.1",
|
"merge": ">=1.2.1",
|
||||||
|
@ -18,13 +19,17 @@
|
||||||
"react": "16.8.6",
|
"react": "16.8.6",
|
||||||
"react-native": "0.59.3",
|
"react-native": "0.59.3",
|
||||||
"@react-native-community/async-storage": "^1.2.2",
|
"@react-native-community/async-storage": "^1.2.2",
|
||||||
|
"react-native-camera": "^2.11.0",
|
||||||
"react-native-country-picker-modal": "^0.6.2",
|
"react-native-country-picker-modal": "^0.6.2",
|
||||||
|
"react-native-document-picker": "^2.3.0",
|
||||||
"react-native-exception-handler": "2.9.0",
|
"react-native-exception-handler": "2.9.0",
|
||||||
"react-native-fast-image": "^5.0.3",
|
"react-native-fast-image": "^5.0.3",
|
||||||
|
"react-native-fs": "^2.13.3",
|
||||||
"react-native-gesture-handler": "^1.1.0",
|
"react-native-gesture-handler": "^1.1.0",
|
||||||
"react-native-image-zoom-viewer": "^2.2.5",
|
"react-native-image-zoom-viewer": "^2.2.5",
|
||||||
"react-native-password-strength-meter": "^0.0.2",
|
"react-native-password-strength-meter": "^0.0.2",
|
||||||
"react-native-phone-input": "lbryio/react-native-phone-input",
|
"react-native-phone-input": "lbryio/react-native-phone-input",
|
||||||
|
"react-native-super-grid": "^3.0.4",
|
||||||
"react-native-vector-icons": "^6.4.2",
|
"react-native-vector-icons": "^6.4.2",
|
||||||
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
|
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
|
||||||
"react-navigation": "^3.11.0",
|
"react-navigation": "^3.11.0",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import DownloadsPage from 'page/downloads';
|
||||||
import DrawerContent from 'component/drawerContent';
|
import DrawerContent from 'component/drawerContent';
|
||||||
import FilePage from 'page/file';
|
import FilePage from 'page/file';
|
||||||
import FirstRunScreen from 'page/firstRun';
|
import FirstRunScreen from 'page/firstRun';
|
||||||
|
import PublishPage from 'page/publish';
|
||||||
import RewardsPage from 'page/rewards';
|
import RewardsPage from 'page/rewards';
|
||||||
import TrendingPage from 'page/trending';
|
import TrendingPage from 'page/trending';
|
||||||
import SearchPage from 'page/search';
|
import SearchPage from 'page/search';
|
||||||
|
@ -148,6 +149,12 @@ const drawer = createDrawerNavigator(
|
||||||
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
|
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Publish: {
|
||||||
|
screen: PublishPage,
|
||||||
|
navigationOptions: {
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="upload" size={20} style={{ color: tintColor }} />,
|
||||||
|
},
|
||||||
|
},
|
||||||
Rewards: {
|
Rewards: {
|
||||||
screen: RewardsPage,
|
screen: RewardsPage,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
|
27
app/src/component/channelSelector/index.js
Normal file
27
app/src/component/channelSelector/index.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
selectBalance,
|
||||||
|
selectMyChannelClaims,
|
||||||
|
selectFetchingMyChannels,
|
||||||
|
doFetchChannelListMine,
|
||||||
|
doCreateChannel,
|
||||||
|
doToast,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import ChannelSelector from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
balance: selectBalance(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
notify: data => dispatch(doToast(data)),
|
||||||
|
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
||||||
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(ChannelSelector);
|
241
app/src/component/channelSelector/view.js
Normal file
241
app/src/component/channelSelector/view.js
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { CLAIM_VALUES, isNameValid } from 'lbry-redux';
|
||||||
|
import { ActivityIndicator, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import channelSelectorStyle from 'styles/channelSelector';
|
||||||
|
|
||||||
|
export default class ChannelSelector extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
currentSelectedValue: Constants.ITEM_ANONYMOUS,
|
||||||
|
newChannelName: '',
|
||||||
|
newChannelBid: 0.1,
|
||||||
|
addingChannel: false,
|
||||||
|
creatingChannel: false,
|
||||||
|
newChannelNameError: '',
|
||||||
|
newChannelBidError: '',
|
||||||
|
createChannelError: undefined,
|
||||||
|
showCreateChannel: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { channels, fetchChannelListMine, fetchingChannels } = this.props;
|
||||||
|
if (!channels.length && !fetchingChannels) {
|
||||||
|
fetchChannelListMine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateCancel = () => {
|
||||||
|
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePickerValueChange = (itemValue, itemIndex) => {
|
||||||
|
if (Constants.ITEM_CREATE_A_CHANNEL === itemValue) {
|
||||||
|
this.setState({ showCreateChannel: true });
|
||||||
|
} else {
|
||||||
|
this.handleCreateCancel();
|
||||||
|
this.handleChannelChange(Constants.ITEM_ANONYMOUS === itemValue ? CLAIM_VALUES.CHANNEL_ANONYMOUS : itemValue);
|
||||||
|
}
|
||||||
|
this.setState({ currentSelectedValue: itemValue });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChannelChange = value => {
|
||||||
|
const { onChannelChange } = this.props;
|
||||||
|
const { newChannelBid } = this.state;
|
||||||
|
const channel = value;
|
||||||
|
|
||||||
|
if (channel === CLAIM_VALUES.CHANNEL_NEW) {
|
||||||
|
this.setState({ addingChannel: true });
|
||||||
|
if (onChannelChange) {
|
||||||
|
onChannelChange(channel);
|
||||||
|
}
|
||||||
|
this.handleNewChannelBidChange(newChannelBid);
|
||||||
|
} else {
|
||||||
|
this.setState({ addingChannel: false });
|
||||||
|
if (onChannelChange) {
|
||||||
|
onChannelChange(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNewChannelNameChange = value => {
|
||||||
|
const { notify } = this.props;
|
||||||
|
|
||||||
|
let newChannelName = value;
|
||||||
|
|
||||||
|
if (newChannelName.startsWith('@')) {
|
||||||
|
newChannelName = newChannelName.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
newChannelName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNewChannelBidChange = newChannelBid => {
|
||||||
|
const { balance, notify } = this.props;
|
||||||
|
let newChannelBidError;
|
||||||
|
if (newChannelBid <= 0) {
|
||||||
|
newChannelBidError = __('Please enter a deposit above 0');
|
||||||
|
} else if (newChannelBid === balance) {
|
||||||
|
newChannelBidError = __('Please decrease your deposit to account for transaction fees');
|
||||||
|
} else if (newChannelBid > balance) {
|
||||||
|
newChannelBidError = __('Deposit cannot be higher than your balance');
|
||||||
|
}
|
||||||
|
|
||||||
|
notify({ message: newChannelBidError });
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
newChannelBid,
|
||||||
|
newChannelBidError,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCreateChannelClick = () => {
|
||||||
|
const { balance, createChannel, onChannelChange, notify } = this.props;
|
||||||
|
const { newChannelBid, newChannelName } = this.state;
|
||||||
|
|
||||||
|
if (newChannelName.trim().length === 0 || !isNameValid(newChannelName.substr(1), false)) {
|
||||||
|
notify({ message: 'Your channel name contains invalid characters.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.channelExists(newChannelName)) {
|
||||||
|
notify({ message: 'You have already created a channel with the same name.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newChannelBid > balance) {
|
||||||
|
notify({ message: 'Deposit cannot be higher than your balance' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelName = `@${newChannelName}`;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: true,
|
||||||
|
createChannelError: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const success = () => {
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
addingChannel: false,
|
||||||
|
currentSelectedValue: channelName,
|
||||||
|
showCreateChannel: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onChannelChange) {
|
||||||
|
onChannelChange(channelName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const failure = () => {
|
||||||
|
notify({ message: 'Unable to create channel due to an internal error.' });
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
createChannel(channelName, newChannelBid).then(success, failure);
|
||||||
|
};
|
||||||
|
|
||||||
|
channelExists = name => {
|
||||||
|
const { channels = [] } = this.props;
|
||||||
|
for (let channel of channels) {
|
||||||
|
if (
|
||||||
|
name.toLowerCase() === channel.name.toLowerCase() ||
|
||||||
|
`@${name}`.toLowerCase() === channel.name.toLowerCase()
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
||||||
|
const { fetchingChannels, channels = [] } = this.props;
|
||||||
|
const pickerItems = [{ name: Constants.ITEM_ANONYMOUS }, { name: Constants.ITEM_CREATE_A_CHANNEL }].concat(
|
||||||
|
channels
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
newChannelName,
|
||||||
|
newChannelNameError,
|
||||||
|
newChannelBid,
|
||||||
|
newChannelBidError,
|
||||||
|
creatingChannel,
|
||||||
|
createChannelError,
|
||||||
|
addingChannel,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={channelSelectorStyle.container}>
|
||||||
|
<Picker
|
||||||
|
selectedValue={this.state.currentSelectedValue}
|
||||||
|
style={channelSelectorStyle.channelPicker}
|
||||||
|
itemStyle={channelSelectorStyle.channelPickerItem}
|
||||||
|
onValueChange={this.handlePickerValueChange}
|
||||||
|
>
|
||||||
|
{pickerItems.map(item => (
|
||||||
|
<Picker.Item label={item.name} value={item.name} key={item.name} />
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
|
||||||
|
{this.state.showCreateChannel && (
|
||||||
|
<View style={channelSelectorStyle.createChannelContainer}>
|
||||||
|
<View style={channelSelectorStyle.channelInputContainer}>
|
||||||
|
<Text style={channelSelectorStyle.channelAt}>@</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
style={channelSelectorStyle.channelNameInput}
|
||||||
|
value={this.state.newChannelName}
|
||||||
|
onChangeText={this.handleNewChannelNameChange}
|
||||||
|
placeholder={'Channel name'}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={channelSelectorStyle.bidRow}>
|
||||||
|
<Text style={channelSelectorStyle.label}>Deposit</Text>
|
||||||
|
<TextInput
|
||||||
|
style={channelSelectorStyle.bidAmountInput}
|
||||||
|
value={String(this.state.newChannelBid)}
|
||||||
|
onChangeText={this.handleNewChannelBidChange}
|
||||||
|
placeholder={'0.00'}
|
||||||
|
keyboardType={'number-pad'}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
/>
|
||||||
|
<Text style={channelSelectorStyle.currency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={channelSelectorStyle.helpText}>
|
||||||
|
This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={channelSelectorStyle.buttonContainer}>
|
||||||
|
{creatingChannel && <ActivityIndicator size={'small'} color={Colors.LbryGreen} />}
|
||||||
|
{!creatingChannel && (
|
||||||
|
<View style={channelSelectorStyle.buttons}>
|
||||||
|
<Link style={channelSelectorStyle.cancelLink} text="Cancel" onPress={this.handleCreateCancel} />
|
||||||
|
<Button
|
||||||
|
style={channelSelectorStyle.createButton}
|
||||||
|
disabled={!(this.state.newChannelName.trim().length > 0 && this.state.newChannelBid > 0)}
|
||||||
|
text="Create"
|
||||||
|
onPress={this.handleCreateChannelClick}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,8 @@ class FileItem extends React.PureComponent {
|
||||||
const uri = normalizeURI(this.props.uri);
|
const uri = normalizeURI(this.props.uri);
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
const channelName = claim ? claim.channel_name : null;
|
const signingChannel = claim ? claim.signing_channel : null;
|
||||||
|
const channelName = signingChannel ? signingChannel.name : null;
|
||||||
const channelClaimId =
|
const channelClaimId =
|
||||||
claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
claim && claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
||||||
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
|
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
|
||||||
|
|
|
@ -97,6 +97,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
{!isResolvingUri && (
|
{!isResolvingUri && (
|
||||||
<Text style={fileItemMediaStyle.autothumbText}>
|
<Text style={fileItemMediaStyle.autothumbText}>
|
||||||
{title &&
|
{title &&
|
||||||
|
title.trim().length > 0 &&
|
||||||
title
|
title
|
||||||
.replace(/\s+/g, '')
|
.replace(/\s+/g, '')
|
||||||
.substring(0, Math.min(title.replace(' ', '').length, 5))
|
.substring(0, Math.min(title.replace(' ', '').length, 5))
|
||||||
|
|
|
@ -63,7 +63,8 @@ class FileListItem extends React.PureComponent {
|
||||||
let name, channel, height, channelClaimId, fullChannelUri;
|
let name, channel, height, channelClaimId, fullChannelUri;
|
||||||
if (claim) {
|
if (claim) {
|
||||||
name = claim.name;
|
name = claim.name;
|
||||||
channel = claim.channel_name;
|
signingChannel = claim.signing_channel;
|
||||||
|
channel = signingChannel ? signingChannel.name : null;
|
||||||
height = claim.height;
|
height = claim.height;
|
||||||
channelClaimId = claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
channelClaimId = claim.value && claim.value.publisherSignature && claim.value.publisherSignature.certificateId;
|
||||||
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
|
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
|
||||||
|
|
4
app/src/component/publishRewardsDriver/index.js
Normal file
4
app/src/component/publishRewardsDriver/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PublishRewardsDriver from './view';
|
||||||
|
|
||||||
|
export default connect()(PublishRewardsDriver);
|
20
app/src/component/publishRewardsDriver/view.js
Normal file
20
app/src/component/publishRewardsDriver/view.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, TouchableOpacity } from 'react-native';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import publishStyle from 'styles/publish';
|
||||||
|
|
||||||
|
class PublishRewadsDriver extends React.PureComponent<Props> {
|
||||||
|
render() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={publishStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
|
||||||
|
<Icon name="award" size={16} style={publishStyle.rewardIcon} />
|
||||||
|
<Text style={publishStyle.rewardDriverText}>Earn some credits to be able to publish your content.</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishRewadsDriver;
|
4
app/src/component/tag/index.js
Normal file
4
app/src/component/tag/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Tag from './view';
|
||||||
|
|
||||||
|
export default connect()(Tag);
|
55
app/src/component/tag/view.js
Normal file
55
app/src/component/tag/view.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, TouchableOpacity, View } from 'react-native';
|
||||||
|
import tagStyle from 'styles/tag';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
|
||||||
|
export default class Tag extends React.PureComponent {
|
||||||
|
onPressDefault = () => {
|
||||||
|
const { name, navigation, type, onAddPress, onRemovePress } = this.props;
|
||||||
|
if ('add' === type) {
|
||||||
|
if (onAddPress) {
|
||||||
|
onAddPress(name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ('remove' === type) {
|
||||||
|
if (onRemovePress) {
|
||||||
|
onRemovePress(name);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigation) {
|
||||||
|
// navigate to tag page
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, onPress, style, type } = this.props;
|
||||||
|
|
||||||
|
let styles = [];
|
||||||
|
if (style) {
|
||||||
|
if (style.length) {
|
||||||
|
styles = styles.concat(style);
|
||||||
|
} else {
|
||||||
|
styles.push(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.push({
|
||||||
|
backgroundColor: Colors.TagGreen,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginBottom: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
|
||||||
|
<View style={tagStyle.content}>
|
||||||
|
<Text style={tagStyle.text}>{name}</Text>
|
||||||
|
{type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
app/src/component/tagSearch/index.js
Normal file
12
app/src/component/tagSearch/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectUnfollowedTags } from 'lbry-redux';
|
||||||
|
import TagSearch from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
unfollowedTags: selectUnfollowedTags(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(TagSearch);
|
81
app/src/component/tagSearch/view.js
Normal file
81
app/src/component/tagSearch/view.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
|
import Tag from 'component/tag';
|
||||||
|
import tagStyle from 'styles/tag';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
|
||||||
|
export default class TagSearch extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
tag: null,
|
||||||
|
tagResults: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { selectedTags = [] } = this.props;
|
||||||
|
this.updateTagResults(this.state.tag, selectedTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { selectedTags: prevSelectedTags = [] } = this.props;
|
||||||
|
const { selectedTags = [] } = nextProps;
|
||||||
|
|
||||||
|
if (selectedTags.length !== prevSelectedTags.length) {
|
||||||
|
this.updateTagResults(this.state.tag, selectedTags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddTagPress = tag => {
|
||||||
|
const { handleAddTag } = this.props;
|
||||||
|
if (handleAddTag) {
|
||||||
|
handleAddTag(tag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTagChange = tag => {
|
||||||
|
const { selectedTags = [] } = this.props;
|
||||||
|
this.setState({ tag });
|
||||||
|
this.updateTagResults(tag, selectedTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTagResults = (tag, selectedTags = []) => {
|
||||||
|
const { unfollowedTags } = this.props;
|
||||||
|
|
||||||
|
// the search term should always be the first result
|
||||||
|
let results = [];
|
||||||
|
const tagNotSelected = name => selectedTags.indexOf(name.toLowerCase()) === -1;
|
||||||
|
const suggestedTagsSet = new Set(unfollowedTags.map(tag => tag.name));
|
||||||
|
const suggestedTags = Array.from(suggestedTagsSet).filter(tagNotSelected);
|
||||||
|
if (tag && tag.trim().length > 0) {
|
||||||
|
results.push(tag.toLowerCase());
|
||||||
|
const doesTagMatch = name => name.toLowerCase().includes(tag.toLowerCase());
|
||||||
|
results = results.concat(suggestedTags.filter(doesTagMatch).slice(0, 5));
|
||||||
|
} else {
|
||||||
|
results = results.concat(suggestedTags.slice(0, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ tagResults: results });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, style, type, selectedTags = [] } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<TextInput
|
||||||
|
style={tagStyle.searchInput}
|
||||||
|
placeholder={'Search for more tags'}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
value={this.state.tag}
|
||||||
|
numberOfLines={1}
|
||||||
|
onChangeText={this.handleTagChange}
|
||||||
|
/>
|
||||||
|
<View style={tagStyle.tagResultsList}>
|
||||||
|
{this.state.tagResults.map(tag => (
|
||||||
|
<Tag key={tag} name={tag} style={tagStyle.tag} type="add" onAddPress={name => this.onAddTagPress(name)} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,10 @@ const Constants = {
|
||||||
PHASE_COLLECTION: 'collection',
|
PHASE_COLLECTION: 'collection',
|
||||||
PHASE_VERIFICATION: 'verification',
|
PHASE_VERIFICATION: 'verification',
|
||||||
|
|
||||||
|
PHASE_SELECTOR: 'selector',
|
||||||
|
PHASE_DETAILS: 'details',
|
||||||
|
PHASE_PUBLISH: 'publish',
|
||||||
|
|
||||||
CONTENT_TAB: 'content',
|
CONTENT_TAB: 'content',
|
||||||
ABOUT_TAB: 'about',
|
ABOUT_TAB: 'about',
|
||||||
|
|
||||||
|
@ -48,6 +52,7 @@ const Constants = {
|
||||||
DRAWER_ROUTE_TRENDING: 'Trending',
|
DRAWER_ROUTE_TRENDING: 'Trending',
|
||||||
DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
|
DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
|
||||||
DRAWER_ROUTE_MY_LBRY: 'Downloads',
|
DRAWER_ROUTE_MY_LBRY: 'Downloads',
|
||||||
|
DRAWER_ROUTE_PUBLISH: 'Publish',
|
||||||
DRAWER_ROUTE_REWARDS: 'Rewards',
|
DRAWER_ROUTE_REWARDS: 'Rewards',
|
||||||
DRAWER_ROUTE_WALLET: 'Wallet',
|
DRAWER_ROUTE_WALLET: 'Wallet',
|
||||||
DRAWER_ROUTE_SETTINGS: 'Settings',
|
DRAWER_ROUTE_SETTINGS: 'Settings',
|
||||||
|
@ -63,6 +68,9 @@ const Constants = {
|
||||||
|
|
||||||
ROUTE_FILE: 'File',
|
ROUTE_FILE: 'File',
|
||||||
|
|
||||||
|
ITEM_CREATE_A_CHANNEL: 'Create a channel...',
|
||||||
|
ITEM_ANONYMOUS: 'Publish anonymously',
|
||||||
|
|
||||||
SUBSCRIPTIONS_VIEW_ALL: 'view_all',
|
SUBSCRIPTIONS_VIEW_ALL: 'view_all',
|
||||||
SUBSCRIPTIONS_VIEW_LATEST_FIRST: 'view_latest_first',
|
SUBSCRIPTIONS_VIEW_LATEST_FIRST: 'view_latest_first',
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ import {
|
||||||
fileReducer,
|
fileReducer,
|
||||||
fileInfoReducer,
|
fileInfoReducer,
|
||||||
notificationsReducer,
|
notificationsReducer,
|
||||||
|
publishReducer,
|
||||||
searchReducer,
|
searchReducer,
|
||||||
|
tagsReducer,
|
||||||
walletReducer,
|
walletReducer,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
|
@ -90,11 +92,13 @@ const reducers = combineReducers({
|
||||||
homepage: homepageReducer,
|
homepage: homepageReducer,
|
||||||
nav: navigatorReducer,
|
nav: navigatorReducer,
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
|
publish: publishReducer,
|
||||||
rewards: rewardsReducer,
|
rewards: rewardsReducer,
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
subscriptions: subscriptionsReducer,
|
subscriptions: subscriptionsReducer,
|
||||||
sync: syncReducer,
|
sync: syncReducer,
|
||||||
|
tags: tagsReducer,
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
wallet: walletReducer,
|
wallet: walletReducer,
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { navigateBack, navigateToUri } from 'utils/helper';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
import ImageViewer from 'react-native-image-zoom-viewer';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import Tag from 'component/tag';
|
||||||
import ChannelPage from 'page/channel';
|
import ChannelPage from 'page/channel';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants';
|
||||||
|
@ -502,11 +503,7 @@ class FilePage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTags = tags => {
|
renderTags = tags => {
|
||||||
return tags.map((tag, i) => (
|
return tags.map((tag, i) => <Tag style={filePageStyle.tagItem} key={`${tag}-${i}`} name={tag} />);
|
||||||
<Text style={filePageStyle.tagItem} key={`${tag}-${i}`}>
|
|
||||||
{tag}
|
|
||||||
</Text>
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onFileDownloadButtonPlayed = () => {
|
onFileDownloadButtonPlayed = () => {
|
||||||
|
@ -618,7 +615,8 @@ class FilePage extends React.PureComponent {
|
||||||
const description = metadata.description ? metadata.description : null;
|
const description = metadata.description ? metadata.description : null;
|
||||||
const mediaType = Lbry.getMediaType(contentType);
|
const mediaType = Lbry.getMediaType(contentType);
|
||||||
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
const isPlayable = mediaType === 'video' || mediaType === 'audio';
|
||||||
const { height, channel_name: channelName, value } = claim;
|
const { height, signing_channel: signingChannel, value } = claim;
|
||||||
|
const channelName = signingChannel && signingChannel.name;
|
||||||
const showActions =
|
const showActions =
|
||||||
fileInfo &&
|
fileInfo &&
|
||||||
fileInfo.download_path &&
|
fileInfo.download_path &&
|
||||||
|
|
31
app/src/page/publish/index.js
Normal file
31
app/src/page/publish/index.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doPublish,
|
||||||
|
doResolveUri,
|
||||||
|
doToast,
|
||||||
|
doUploadThumbnail,
|
||||||
|
selectBalance,
|
||||||
|
selectPublishFormValues,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
|
publishFormValues: selectPublishFormValues(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
notify: data => dispatch(doToast(data)),
|
||||||
|
uploadThumbnail: (filePath, fsAdapter) => dispatch(doUploadThumbnail(filePath, null, fsAdapter)),
|
||||||
|
publish: params => dispatch(doPublish(params)),
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH)),
|
||||||
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(PublishPage);
|
890
app/src/page/publish/view.js
Normal file
890
app/src/page/publish/view.js
Normal file
|
@ -0,0 +1,890 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Clipboard,
|
||||||
|
Image,
|
||||||
|
NativeModules,
|
||||||
|
Picker,
|
||||||
|
ScrollView,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import { FlatGrid } from 'react-native-super-grid';
|
||||||
|
import { isNameValid, buildURI, regexInvalidURI, CLAIM_VALUES, LICENSES, THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||||
|
import { DocumentPicker, DocumentPickerUtil } from 'react-native-document-picker';
|
||||||
|
import { RNCamera } from 'react-native-camera';
|
||||||
|
import { generateCombination } from 'gfycat-style-urls';
|
||||||
|
import RNFS from 'react-native-fs';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import ChannelSelector from 'component/channelSelector';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Constants from 'constants';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import Feather from 'react-native-vector-icons/Feather';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import PublishRewardsDriver from 'component/publishRewardsDriver';
|
||||||
|
import Tag from 'component/tag';
|
||||||
|
import TagSearch from 'component/tagSearch';
|
||||||
|
import UriBar from 'component/uriBar';
|
||||||
|
import publishStyle from 'styles/publish';
|
||||||
|
|
||||||
|
const languages = {
|
||||||
|
en: 'English',
|
||||||
|
zh: 'Chinese',
|
||||||
|
fr: 'French',
|
||||||
|
de: 'German',
|
||||||
|
jp: 'Japanese',
|
||||||
|
ru: 'Russian',
|
||||||
|
es: 'Spanish',
|
||||||
|
id: 'Indonesian',
|
||||||
|
it: 'Italian',
|
||||||
|
nl: 'Dutch',
|
||||||
|
tr: 'Turkish',
|
||||||
|
pl: 'Polish',
|
||||||
|
ms: 'Malay',
|
||||||
|
pt: 'Portuguese',
|
||||||
|
vi: 'Vietnamese',
|
||||||
|
th: 'Thai',
|
||||||
|
ar: 'Arabic',
|
||||||
|
cs: 'Czech',
|
||||||
|
hr: 'Croatian',
|
||||||
|
km: 'Cambodian',
|
||||||
|
ko: 'Korean',
|
||||||
|
no: 'Norwegian',
|
||||||
|
ro: 'Romanian',
|
||||||
|
hi: 'Hindi',
|
||||||
|
el: 'Greek',
|
||||||
|
};
|
||||||
|
|
||||||
|
class PublishPage extends React.PureComponent {
|
||||||
|
camera = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
canUseCamera: false,
|
||||||
|
titleFocused: false,
|
||||||
|
descriptionFocused: false,
|
||||||
|
|
||||||
|
// gallery videos
|
||||||
|
videos: null,
|
||||||
|
|
||||||
|
// camera
|
||||||
|
cameraType: RNCamera.Constants.Type.back,
|
||||||
|
videoRecordingMode: false,
|
||||||
|
recordingVideo: false,
|
||||||
|
showCameraOverlay: false,
|
||||||
|
|
||||||
|
// paths and media
|
||||||
|
uploadsPath: null,
|
||||||
|
thumbnailPath: null,
|
||||||
|
currentMedia: null,
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
|
currentPhase: Constants.PHASE_SELECTOR,
|
||||||
|
|
||||||
|
// publish
|
||||||
|
advancedMode: false,
|
||||||
|
anonymous: true,
|
||||||
|
channelName: CLAIM_VALUES.CHANNEL_ANONYMOUS,
|
||||||
|
priceSet: false,
|
||||||
|
|
||||||
|
// input data
|
||||||
|
bid: 0.1,
|
||||||
|
description: null,
|
||||||
|
title: null,
|
||||||
|
language: 'en',
|
||||||
|
license: LICENSES.NONE,
|
||||||
|
mature: false,
|
||||||
|
name: null,
|
||||||
|
price: 0,
|
||||||
|
uri: null,
|
||||||
|
tags: [],
|
||||||
|
uploadedThumbnailUri: null,
|
||||||
|
|
||||||
|
// other
|
||||||
|
publishStarted: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
didFocusListener;
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.didFocusListener) {
|
||||||
|
this.didFocusListener.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewUri(name, channel) {
|
||||||
|
const { resolveUri } = this.props;
|
||||||
|
// If they are midway through a channel creation, treat it as anonymous until it completes
|
||||||
|
const channelName =
|
||||||
|
channel === CLAIM_VALUES.CHANNEL_ANONYMOUS || channel === CLAIM_VALUES.CHANNEL_NEW ? '' : channel;
|
||||||
|
|
||||||
|
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
|
||||||
|
let uri;
|
||||||
|
try {
|
||||||
|
uri = buildURI({ contentName: name, channelName });
|
||||||
|
} catch (e) {
|
||||||
|
// something wrong with channel or name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
if (channelName) {
|
||||||
|
// resolve without the channel name so we know the winning bid for it
|
||||||
|
const uriLessChannel = buildURI({ contentName: name });
|
||||||
|
resolveUri(uriLessChannel);
|
||||||
|
}
|
||||||
|
resolveUri(uri);
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModePressed = () => {
|
||||||
|
this.setState({ advancedMode: !this.state.advancedMode });
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePublishPressed = () => {
|
||||||
|
const { notify, publish } = this.props;
|
||||||
|
const {
|
||||||
|
bid,
|
||||||
|
channelName,
|
||||||
|
currentMedia,
|
||||||
|
description,
|
||||||
|
language,
|
||||||
|
license,
|
||||||
|
mature,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
priceSet,
|
||||||
|
tags,
|
||||||
|
title,
|
||||||
|
uploadedThumbnailUri: thumbnail,
|
||||||
|
uri,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (!title || title.trim().length === 0) {
|
||||||
|
notify({ message: 'Please provide a title' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
notify({ message: 'Please specify an address where people can find your content.' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishTags = tags.slice();
|
||||||
|
if (mature) {
|
||||||
|
publishTags.push('nsfw');
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishParams = {
|
||||||
|
filePath: currentMedia.filePath,
|
||||||
|
bid: bid || 0.1,
|
||||||
|
tags: publishTags,
|
||||||
|
title: title || '',
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
description: description || '',
|
||||||
|
language,
|
||||||
|
nsfw: mature,
|
||||||
|
license,
|
||||||
|
licenseUrl: '',
|
||||||
|
otherLicenseDescription: '',
|
||||||
|
name: name || undefined,
|
||||||
|
contentIsFree: !priceSet,
|
||||||
|
fee: { currency: 'LBC', price },
|
||||||
|
uri: uri || undefined,
|
||||||
|
channel_name: CLAIM_VALUES.CHANNEL_ANONYMOUS === channelName ? undefined : channelName,
|
||||||
|
isStillEditing: false,
|
||||||
|
claim: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({ publishStarted: true }, () => publish(publishParams));
|
||||||
|
};
|
||||||
|
|
||||||
|
onComponentFocused = () => {
|
||||||
|
const { pushDrawerStack, setPlayerVisible } = this.props;
|
||||||
|
|
||||||
|
pushDrawerStack();
|
||||||
|
setPlayerVisible();
|
||||||
|
|
||||||
|
NativeModules.Gallery.canUseCamera().then(canUseCamera => this.setState({ canUseCamera }));
|
||||||
|
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => this.setState({ thumbnailPath }));
|
||||||
|
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos }));
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.onComponentFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { currentRoute, publishFormValues } = nextProps;
|
||||||
|
const { currentRoute: prevRoute } = this.props;
|
||||||
|
|
||||||
|
if (Constants.DRAWER_ROUTE_PUBLISH === currentRoute && currentRoute !== prevRoute) {
|
||||||
|
this.onComponentFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publishFormValues) {
|
||||||
|
if (publishFormValues.thumbnail && !this.state.uploadedThumbnailUri) {
|
||||||
|
this.setState({ uploadedThumbnailUri: publishFormValues.thumbnail });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.publishStarted) {
|
||||||
|
if (publishFormValues.publishSuccess) {
|
||||||
|
this.setState({ publishStarted: false, currentPhase: Constants.PHASE_PUBLISH });
|
||||||
|
} else if (publishFormValues.publishError) {
|
||||||
|
// TODO: Display error if any
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!publishFormValues.publishing && this.state.publishStarted) {
|
||||||
|
this.setState({ publishStarted: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentMedia(media) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
currentMedia: media,
|
||||||
|
title: media.name,
|
||||||
|
name: this.formatNameForTitle(media.name),
|
||||||
|
currentPhase: Constants.PHASE_DETAILS,
|
||||||
|
},
|
||||||
|
() => this.handleNameChange(this.state.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatNameForTitle = title => {
|
||||||
|
return title.replace(regexInvalidURI, '-').toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
showSelector() {
|
||||||
|
this.setState({
|
||||||
|
publishStarted: false,
|
||||||
|
|
||||||
|
currentMedia: null,
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
currentPhase: Constants.PHASE_SELECTOR,
|
||||||
|
|
||||||
|
// publish
|
||||||
|
advancedMode: false,
|
||||||
|
anonymous: true,
|
||||||
|
channelName: CLAIM_VALUES.CHANNEL_ANONYMOUS,
|
||||||
|
priceSet: false,
|
||||||
|
|
||||||
|
// input data
|
||||||
|
bid: 0.1,
|
||||||
|
description: null,
|
||||||
|
title: null,
|
||||||
|
language: 'en',
|
||||||
|
license: LICENSES.NONE,
|
||||||
|
name: null,
|
||||||
|
price: 0,
|
||||||
|
uri: null,
|
||||||
|
tags: [],
|
||||||
|
uploadedThumbnailUri: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRecordVideoPressed = () => {
|
||||||
|
if (!this.state.showCameraOverlay) {
|
||||||
|
this.setState({ canUseCamera: true, showCameraOverlay: true, videoRecordingMode: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTakePhotoPressed = () => {
|
||||||
|
if (!this.state.showCameraOverlay) {
|
||||||
|
this.setState({ canUseCamera: true, showCameraOverlay: true, videoRecordingMode: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCloseCameraPressed = () => {
|
||||||
|
this.setState({ showCameraOverlay: false, videoRecordingMode: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
getFilePathFromUri = uri => {
|
||||||
|
return uri.substring('file://'.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCameraActionPressed = () => {
|
||||||
|
// check if it's video or photo mode
|
||||||
|
if (this.state.videoRecordingMode) {
|
||||||
|
if (this.state.recordingVideo) {
|
||||||
|
this.camera.stopRecording();
|
||||||
|
} else {
|
||||||
|
this.setState({ recordingVideo: true });
|
||||||
|
|
||||||
|
const options = { quality: RNCamera.Constants.VideoQuality['1080p'] };
|
||||||
|
this.camera.recordAsync(options).then(data => {
|
||||||
|
this.setState({ recordingVideo: false });
|
||||||
|
const currentMedia = {
|
||||||
|
id: -1,
|
||||||
|
filePath: this.getFilePathFromUri(data.uri),
|
||||||
|
name: generateCombination(2, ' ', true),
|
||||||
|
type: 'video/mp4', // always MP4
|
||||||
|
duration: 0,
|
||||||
|
};
|
||||||
|
this.setCurrentMedia(currentMedia);
|
||||||
|
this.setState({
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
|
currentPhase: Constants.PHASE_DETAILS,
|
||||||
|
showCameraOverlay: false,
|
||||||
|
videoRecordingMode: false,
|
||||||
|
recordingVideo: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const options = { quality: 0.7 };
|
||||||
|
this.camera.takePictureAsync(options).then(data => {
|
||||||
|
const currentMedia = {
|
||||||
|
id: -1,
|
||||||
|
filePath: this.getFilePathFromUri(data.uri),
|
||||||
|
name: generateCombination(2, ' ', true),
|
||||||
|
type: 'image/jpg', // always JPEG
|
||||||
|
duration: 0,
|
||||||
|
};
|
||||||
|
this.setCurrentMedia(currentMedia);
|
||||||
|
this.setState({
|
||||||
|
currentPhase: Constants.PHASE_DETAILS,
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
|
showCameraOverlay: false,
|
||||||
|
videoRecordingMode: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSwitchCameraPressed = () => {
|
||||||
|
const { cameraType } = this.state;
|
||||||
|
this.setState({
|
||||||
|
cameraType:
|
||||||
|
cameraType === RNCamera.Constants.Type.back ? RNCamera.Constants.Type.front : RNCamera.Constants.Type.back,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleUploadPressed = () => {
|
||||||
|
DocumentPicker.show(
|
||||||
|
{
|
||||||
|
filetype: [DocumentPickerUtil.allFiles()],
|
||||||
|
},
|
||||||
|
(error, res) => {
|
||||||
|
if (!error) {
|
||||||
|
//console.log(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
getRandomFileId = () => {
|
||||||
|
// generate a random id for a photo or recorded video between 1 and 20 (for creating thumbnails)
|
||||||
|
const id = Math.floor(Math.random() * (20 - 2)) + 1;
|
||||||
|
return '_' + id;
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePublishAgainPressed = () => {
|
||||||
|
this.showSelector();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBidChange = bid => {
|
||||||
|
this.setState({ bid });
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePriceChange = price => {
|
||||||
|
this.setState({ price });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNameChange = name => {
|
||||||
|
const { notify } = this.props;
|
||||||
|
this.setState({ name });
|
||||||
|
if (!isNameValid(name, false)) {
|
||||||
|
notify({ message: 'Your content address contains invalid characters' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uri = this.getNewUri(name, this.state.channelName);
|
||||||
|
this.setState({ uri });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChannelChanged = channel => {
|
||||||
|
this.setState({ channelName: channel });
|
||||||
|
const uri = this.getNewUri(name, this.state.channelName);
|
||||||
|
this.setState({ uri });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleAddTag = tag => {
|
||||||
|
if (!tag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { notify } = this.props;
|
||||||
|
const { tags } = this.state;
|
||||||
|
const index = tags.indexOf(tag.toLowerCase());
|
||||||
|
if (index === -1) {
|
||||||
|
const newTags = tags.slice();
|
||||||
|
newTags.push(tag);
|
||||||
|
this.setState({ tags: newTags });
|
||||||
|
} else {
|
||||||
|
notify({ message: `You already added the "${tag}" tag.` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRemoveTag = tag => {
|
||||||
|
if (!tag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTags = this.state.tags.slice();
|
||||||
|
const index = newTags.indexOf(tag.toLowerCase());
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
newTags.splice(index, 1);
|
||||||
|
this.setState({ tags: newTags });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateThumbnailUriForMedia = media => {
|
||||||
|
if (this.state.updatingThumbnailUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { notify, uploadThumbnail } = this.props;
|
||||||
|
const { thumbnailPath } = this.state;
|
||||||
|
|
||||||
|
this.setState({ updatingThumbnailUri: true });
|
||||||
|
|
||||||
|
if (media.type) {
|
||||||
|
const mediaType = media.type.substring(0, 5);
|
||||||
|
const tempId = this.getRandomFileId();
|
||||||
|
|
||||||
|
if ('video' === mediaType && media.id > -1) {
|
||||||
|
const uri = `file://${thumbnailPath}/${media.id}.png`;
|
||||||
|
this.setState({ currentThumbnailUri: uri, updatingThumbnailUri: false });
|
||||||
|
|
||||||
|
// upload the thumbnail
|
||||||
|
if (!this.state.uploadedThumbnailUri) {
|
||||||
|
this.setState({ uploadThumbnailStarted: true }, () => uploadThumbnail(this.getFilePathFromUri(uri), RNFS));
|
||||||
|
}
|
||||||
|
} else if ('image' === mediaType || 'video' === mediaType) {
|
||||||
|
const create =
|
||||||
|
'image' === mediaType
|
||||||
|
? NativeModules.Gallery.createImageThumbnail
|
||||||
|
: NativeModules.Gallery.createVideoThumbnail;
|
||||||
|
create(tempId, media.filePath)
|
||||||
|
.then(path => {
|
||||||
|
this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false });
|
||||||
|
if (!this.state.uploadedThumbnailUri) {
|
||||||
|
this.setState({ uploadThumbnailStarted: true }, () => uploadThumbnail(path, RNFS));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
notify({ message: err });
|
||||||
|
this.setState({ updatingThumbnailUri: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTitleChange = title => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
name: this.formatNameForTitle(title),
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.handleNameChange(this.state.name);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { balance, navigation, notify, publishFormValues } = this.props;
|
||||||
|
const { thumbnailPath } = this.state;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (Constants.PHASE_SELECTOR === this.state.currentPhase) {
|
||||||
|
content = (
|
||||||
|
<View style={publishStyle.gallerySelector}>
|
||||||
|
<View style={publishStyle.actionsView}>
|
||||||
|
{this.state.canUseCamera && (
|
||||||
|
<RNCamera style={publishStyle.cameraPreview} type={RNCamera.Constants.Type.back} />
|
||||||
|
)}
|
||||||
|
<View style={publishStyle.actionsSubView}>
|
||||||
|
<TouchableOpacity style={publishStyle.record} onPress={this.handleRecordVideoPressed}>
|
||||||
|
<Icon name="video" size={48} color={Colors.White} />
|
||||||
|
<Text style={publishStyle.actionText}>Record</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View style={publishStyle.subActions}>
|
||||||
|
<TouchableOpacity style={publishStyle.photo} onPress={this.handleTakePhotoPressed}>
|
||||||
|
<Icon name="camera" size={48} color={Colors.White} />
|
||||||
|
<Text style={publishStyle.actionText}>Take a photo</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{false && (
|
||||||
|
<TouchableOpacity style={publishStyle.upload} onPress={this.handleUploadPressed}>
|
||||||
|
<Icon name="file-upload" size={48} color={Colors.White} />
|
||||||
|
<Text style={publishStyle.actionText}>Upload a file</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{(!this.state.videos || !thumbnailPath) && (
|
||||||
|
<View style={publishStyle.loadingView}>
|
||||||
|
<ActivityIndicator size="large" color={Colors.LbryGreen} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{this.state.videos && thumbnailPath && (
|
||||||
|
<FlatGrid
|
||||||
|
style={publishStyle.galleryGrid}
|
||||||
|
itemDimension={134}
|
||||||
|
spacing={2}
|
||||||
|
items={this.state.videos}
|
||||||
|
renderItem={({ item, index }) => {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity key={index} onPress={() => this.setCurrentMedia(item)}>
|
||||||
|
<FastImage
|
||||||
|
style={publishStyle.galleryGridImage}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
source={{ uri: `file://${thumbnailPath}/${item.id}.png` }}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
} else if (Constants.PHASE_DETAILS === this.state.currentPhase && this.state.currentMedia) {
|
||||||
|
const { currentMedia, currentThumbnailUri } = this.state;
|
||||||
|
if (!currentThumbnailUri) {
|
||||||
|
this.updateThumbnailUriForMedia(currentMedia);
|
||||||
|
}
|
||||||
|
content = (
|
||||||
|
<ScrollView style={publishStyle.publishDetails}>
|
||||||
|
{currentThumbnailUri && currentThumbnailUri.trim().length > 0 && (
|
||||||
|
<View style={publishStyle.mainThumbnailContainer}>
|
||||||
|
<FastImage
|
||||||
|
style={publishStyle.mainThumbnail}
|
||||||
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
|
source={{ uri: currentThumbnailUri }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{balance < 0.1 && <PublishRewardsDriver navigation={navigation} />}
|
||||||
|
|
||||||
|
{this.state.uploadThumbnailStarted && !this.state.uploadedThumbnailUri && (
|
||||||
|
<View style={publishStyle.thumbnailUploadContainer}>
|
||||||
|
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
||||||
|
<Text style={publishStyle.thumbnailUploadText}>Uploading thumbnail...</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<View style={publishStyle.textInputLayout}>
|
||||||
|
{(this.state.titleFocused || (this.state.title != null && this.state.title.trim().length > 0)) && (
|
||||||
|
<Text style={publishStyle.textInputTitle}>Title</Text>
|
||||||
|
)}
|
||||||
|
<TextInput
|
||||||
|
placeholder={this.state.titleFocused ? '' : 'Title'}
|
||||||
|
style={publishStyle.inputText}
|
||||||
|
value={this.state.title}
|
||||||
|
numberOfLines={1}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
onChangeText={this.handleTitleChange}
|
||||||
|
onFocus={() => this.setState({ titleFocused: true })}
|
||||||
|
onBlur={() => this.setState({ titleFocused: false })}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={publishStyle.textInputLayout}>
|
||||||
|
{(this.state.descriptionFocused ||
|
||||||
|
(this.state.description != null && this.state.description.trim().length > 0)) && (
|
||||||
|
<Text style={publishStyle.textInputTitle}>Description</Text>
|
||||||
|
)}
|
||||||
|
<TextInput
|
||||||
|
placeholder={this.state.descriptionFocused ? '' : 'Description'}
|
||||||
|
style={publishStyle.inputText}
|
||||||
|
value={this.state.description}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
onChangeText={this.handleDescriptionChange}
|
||||||
|
onFocus={() => this.setState({ descriptionFocused: true })}
|
||||||
|
onBlur={() => this.setState({ descriptionFocused: false })}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<Text style={publishStyle.cardTitle}>Tags</Text>
|
||||||
|
<View style={publishStyle.tagList}>
|
||||||
|
{this.state.tags &&
|
||||||
|
this.state.tags.map(tag => (
|
||||||
|
<Tag
|
||||||
|
key={tag}
|
||||||
|
name={tag}
|
||||||
|
type={'remove'}
|
||||||
|
style={publishStyle.tag}
|
||||||
|
onRemovePress={this.handleRemoveTag}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<TagSearch handleAddTag={this.handleAddTag} selectedTags={this.state.tags} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<Text style={publishStyle.cardTitle}>Channel</Text>
|
||||||
|
|
||||||
|
<ChannelSelector onChannelChange={this.handleChannelChange} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<View style={publishStyle.titleRow}>
|
||||||
|
<Text style={publishStyle.cardTitle}>Price</Text>
|
||||||
|
<View style={publishStyle.switchTitleRow}>
|
||||||
|
<Switch value={this.state.priceSet} onValueChange={value => this.setState({ priceSet: value })} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{!this.state.priceSet && (
|
||||||
|
<Text style={publishStyle.cardText}>Your content will be free. Press the toggle to set a price.</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.priceSet && (
|
||||||
|
<View style={[publishStyle.inputRow, publishStyle.priceInputRow]}>
|
||||||
|
<TextInput
|
||||||
|
placeholder={'0.00'}
|
||||||
|
keyboardType={'number-pad'}
|
||||||
|
style={publishStyle.priceInput}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
numberOfLines={1}
|
||||||
|
value={String(this.state.price)}
|
||||||
|
onChangeText={this.handlePriceChange}
|
||||||
|
/>
|
||||||
|
<Text style={publishStyle.currency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{this.state.advancedMode && (
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<Text style={publishStyle.cardTitle}>Content Address</Text>
|
||||||
|
<Text style={publishStyle.helpText}>
|
||||||
|
The address where people can find your content (ex. lbry://myvideo)
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
placeholder={'lbry://'}
|
||||||
|
style={publishStyle.inputText}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
numberOfLines={1}
|
||||||
|
value={this.state.name}
|
||||||
|
onChangeText={this.handleNameChange}
|
||||||
|
/>
|
||||||
|
<View style={publishStyle.inputRow}>
|
||||||
|
<TextInput
|
||||||
|
placeholder={'0.00'}
|
||||||
|
style={publishStyle.priceInput}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
numberOfLines={1}
|
||||||
|
keyboardType={'numeric'}
|
||||||
|
value={String(this.state.bid)}
|
||||||
|
onChangeText={this.handleBidChange}
|
||||||
|
/>
|
||||||
|
<Text style={publishStyle.currency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={publishStyle.helpText}>
|
||||||
|
This LBC remains yours and the deposit can be undone at any time.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.advancedMode && (
|
||||||
|
<View style={publishStyle.card}>
|
||||||
|
<Text style={publishStyle.cardTitle}>Additional Options</Text>
|
||||||
|
<View style={publishStyle.toggleField}>
|
||||||
|
<Switch value={this.state.mature} onValueChange={value => this.setState({ mature: value })} />
|
||||||
|
<Text style={publishStyle.toggleText}>Mature content</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text style={publishStyle.cardText}>Language</Text>
|
||||||
|
<Picker
|
||||||
|
selectedValue={this.state.language}
|
||||||
|
style={publishStyle.picker}
|
||||||
|
itemStyle={publishStyle.pickerItem}
|
||||||
|
onValueChange={this.handleLanguageValueChange}
|
||||||
|
>
|
||||||
|
{Object.keys(languages).map(lang => (
|
||||||
|
<Picker.Item label={languages[lang]} value={lang} key={lang} />
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<Text style={publishStyle.cardText}>License</Text>
|
||||||
|
<Picker
|
||||||
|
selectedValue={this.state.license}
|
||||||
|
style={publishStyle.picker}
|
||||||
|
itemStyle={publishStyle.pickerItem}
|
||||||
|
onValueChange={this.handleLicenseValueChange}
|
||||||
|
>
|
||||||
|
<Picker.Item label={'None'} value={LICENSES.NONE} key={LICENSES.NONE} />
|
||||||
|
<Picker.Item label={'Public Domain'} value={LICENSES.PUBLIC_DOMAIN} key={LICENSES.PUBLIC_DOMAIN} />
|
||||||
|
{LICENSES.CC_LICENSES.map(({ value, url }) => (
|
||||||
|
<Picker.Item label={value} value={value} key={value} />
|
||||||
|
))}
|
||||||
|
<Picker.Item label={'Copyrighted...'} value={LICENSES.COPYRIGHT} key={LICENSES.COPYRIGHT} />
|
||||||
|
<Picker.Item label={'Other...'} value={LICENSES.OTHER} key={LICENSES.OTHER} />
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={publishStyle.toggleContainer}>
|
||||||
|
<Link
|
||||||
|
text={this.state.advancedMode ? 'Hide extra fields' : 'Show extra fields'}
|
||||||
|
onPress={this.handleModePressed}
|
||||||
|
style={publishStyle.modeLink}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={publishStyle.actionButtons}>
|
||||||
|
{(this.state.publishStarted || publishFormValues.publishing) && (
|
||||||
|
<View style={publishStyle.progress}>
|
||||||
|
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!publishFormValues.publishing && !this.state.publishStarted && (
|
||||||
|
<Link
|
||||||
|
style={publishStyle.cancelLink}
|
||||||
|
text="Cancel"
|
||||||
|
onPress={() => this.setState({ currentPhase: Constants.PHASE_SELECTOR })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!publishFormValues.publishing && !this.state.publishStarted && (
|
||||||
|
<View style={publishStyle.rightActionButtons}>
|
||||||
|
<Button
|
||||||
|
style={publishStyle.publishButton}
|
||||||
|
disabled={balance < 0.1 || !this.state.uploadedThumbnailUri}
|
||||||
|
text="Publish"
|
||||||
|
onPress={this.handlePublishPressed}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
} else if (Constants.PHASE_PUBLISH === this.state.currentPhase) {
|
||||||
|
content = (
|
||||||
|
<ScrollView style={publishStyle.publishDetails}>
|
||||||
|
<View style={publishStyle.successContainer}>
|
||||||
|
<Text style={publishStyle.successTitle}>Success!</Text>
|
||||||
|
<Text style={publishStyle.successText}>Congratulations! Your content was successfully uploaded.</Text>
|
||||||
|
<View style={publishStyle.successRow}>
|
||||||
|
<Link style={publishStyle.successUrl} text={this.state.uri} href={this.state.uri} />
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
Clipboard.setString(this.state.uri);
|
||||||
|
notify({ message: 'Copied.' });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="clipboard" size={24} color={Colors.LbryGreen} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
<Text style={publishStyle.successText}>
|
||||||
|
Your content will be live in a few minutes. In the mean time, feel free to publish more content or explore
|
||||||
|
the app.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={publishStyle.actionButtons}>
|
||||||
|
<Button style={publishStyle.publishButton} text="Publish again" onPress={this.handlePublishAgainPressed} />
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={publishStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
{content}
|
||||||
|
{false && Constants.PHASE_SELECTOR !== this.state.currentPhase && (
|
||||||
|
<FloatingWalletBalance navigation={navigation} />
|
||||||
|
)}
|
||||||
|
{this.state.canUseCamera && this.state.showCameraOverlay && (
|
||||||
|
<View style={publishStyle.cameraOverlay}>
|
||||||
|
<RNCamera
|
||||||
|
style={publishStyle.fullCamera}
|
||||||
|
ref={ref => {
|
||||||
|
this.camera = ref;
|
||||||
|
}}
|
||||||
|
type={this.state.cameraType}
|
||||||
|
flashMode={RNCamera.Constants.FlashMode.off}
|
||||||
|
androidCameraPermissionOptions={{
|
||||||
|
title: 'Camera',
|
||||||
|
message: 'Please grant access to make use of your camera',
|
||||||
|
buttonPositive: 'OK',
|
||||||
|
buttonNegative: 'Cancel',
|
||||||
|
}}
|
||||||
|
androidRecordAudioPermissionOptions={{
|
||||||
|
title: 'Audio',
|
||||||
|
message: 'Please grant access to record audio',
|
||||||
|
buttonPositive: 'OK',
|
||||||
|
buttonNegative: 'Cancel',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
publishStyle.cameraControls,
|
||||||
|
this.state.videoRecordingMode ? publishStyle.transparentControls : publishStyle.opaqueControls,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={publishStyle.controlsRow}>
|
||||||
|
<TouchableOpacity onPress={this.handleCloseCameraPressed}>
|
||||||
|
<Icon name="arrow-left" size={28} color={Colors.White} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={publishStyle.mainControlsRow}>
|
||||||
|
<TouchableOpacity style={publishStyle.switchCameraToggle} onPress={this.handleSwitchCameraPressed}>
|
||||||
|
<Feather name="rotate-cw" size={36} color={Colors.White} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity onPress={this.handleCameraActionPressed}>
|
||||||
|
<View style={publishStyle.cameraAction}>
|
||||||
|
<Feather style={publishStyle.cameraActionIcon} name="circle" size={72} color={Colors.White} />
|
||||||
|
{this.state.recordingVideo && (
|
||||||
|
<Icon
|
||||||
|
style={publishStyle.recordingIcon}
|
||||||
|
name="circle"
|
||||||
|
solid={true}
|
||||||
|
size={44}
|
||||||
|
color={Colors.Red}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishPage;
|
70
app/src/styles/channelSelector.js
Normal file
70
app/src/styles/channelSelector.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import Colors from './colors';
|
||||||
|
|
||||||
|
const channelSelectorStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
channelPicker: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
height: 52,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
bidRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
channelNameInput: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
paddingLeft: 20,
|
||||||
|
},
|
||||||
|
bidAmountInput: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: 16,
|
||||||
|
textAlign: 'right',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
helpText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
createChannelContainer: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
channelAt: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 4,
|
||||||
|
top: 13,
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 16,
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
cancelLink: {
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
createButton: {
|
||||||
|
backgroundColor: Colors.NextLbryGreen,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default channelSelectorStyle;
|
|
@ -3,11 +3,13 @@ const Colors = {
|
||||||
|
|
||||||
Black: '#000000',
|
Black: '#000000',
|
||||||
ChannelGrey: '#9b9b9b',
|
ChannelGrey: '#9b9b9b',
|
||||||
|
DarkerGrey: '#222222',
|
||||||
DarkGrey: '#555555',
|
DarkGrey: '#555555',
|
||||||
DescriptionGrey: '#999999',
|
DescriptionGrey: '#999999',
|
||||||
LbryGreen: '#2f9176',
|
LbryGreen: '#2f9176',
|
||||||
BrighterLbryGreen: '#40b887',
|
BrighterLbryGreen: '#40b887',
|
||||||
NextLbryGreen: '#38d9a9',
|
NextLbryGreen: '#38d9a9',
|
||||||
|
TagGreen: '#e3f6f1',
|
||||||
LightGrey: '#cccccc',
|
LightGrey: '#cccccc',
|
||||||
LighterGrey: '#e5e5e5',
|
LighterGrey: '#e5e5e5',
|
||||||
Orange: '#ffbb00',
|
Orange: '#ffbb00',
|
||||||
|
|
|
@ -322,14 +322,16 @@ const filePageStyle = StyleSheet.create({
|
||||||
tagTitle: {
|
tagTitle: {
|
||||||
fontFamily: 'Inter-UI-SemiBold',
|
fontFamily: 'Inter-UI-SemiBold',
|
||||||
flex: 0.2,
|
flex: 0.2,
|
||||||
|
marginTop: 4,
|
||||||
},
|
},
|
||||||
tagList: {
|
tagList: {
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
flex: 0.8,
|
flex: 0.8,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
tagItem: {
|
tagItem: {
|
||||||
marginRight: 16,
|
marginRight: 4,
|
||||||
},
|
},
|
||||||
rewardDriverCard: {
|
rewardDriverCard: {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
331
app/src/styles/publish.js
Normal file
331
app/src/styles/publish.js
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import Colors from './colors';
|
||||||
|
|
||||||
|
const publishStyle = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: Colors.PageBackground,
|
||||||
|
},
|
||||||
|
gallerySelector: {
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 62,
|
||||||
|
paddingTop: 2,
|
||||||
|
backgroundColor: Colors.DarkGrey,
|
||||||
|
},
|
||||||
|
galleryGrid: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
galleryGridImage: {
|
||||||
|
width: 134,
|
||||||
|
height: 90,
|
||||||
|
},
|
||||||
|
inputText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: Colors.White,
|
||||||
|
marginTop: 16,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
actionButtons: {
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
marginTop: 24,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
rightActionButtons: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
modeLink: {
|
||||||
|
color: Colors.LbryGreen,
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
publishButton: {
|
||||||
|
backgroundColor: Colors.LbryGreen,
|
||||||
|
alignSelf: 'flex-end',
|
||||||
|
},
|
||||||
|
cardTitle: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 20,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
actionsView: {
|
||||||
|
width: '100%',
|
||||||
|
height: 240,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
flex: 0.5,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
subActions: {
|
||||||
|
flex: 0.5,
|
||||||
|
borderLeftWidth: 2,
|
||||||
|
borderLeftColor: Colors.DarkerGrey,
|
||||||
|
},
|
||||||
|
actionText: {
|
||||||
|
color: Colors.White,
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
photo: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
height: 240,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
backgroundColor: Colors.Black,
|
||||||
|
height: 120,
|
||||||
|
borderTopWidth: 2,
|
||||||
|
borderTopColor: Colors.DarkerGrey,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
publishDetails: {
|
||||||
|
marginTop: 60,
|
||||||
|
},
|
||||||
|
mainThumbnailContainer: {
|
||||||
|
backgroundColor: Colors.Black,
|
||||||
|
width: '100%',
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
mainThumbnail: {
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
inputRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
priceInput: {
|
||||||
|
width: 80,
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
currency: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
},
|
||||||
|
cardRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
switchRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 24,
|
||||||
|
},
|
||||||
|
switchTitleRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 24,
|
||||||
|
marginTop: -10,
|
||||||
|
},
|
||||||
|
switchText: {
|
||||||
|
marginRight: 4,
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
loadingView: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
titleRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
cardText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
cameraPreview: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
actionsSubView: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
successContainer: {
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
successTitle: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 28,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
successText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
successRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
successUrl: {
|
||||||
|
flex: 0.9,
|
||||||
|
fontSize: 32,
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
color: Colors.NextLbryGreen,
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
cameraOverlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: Colors.Black,
|
||||||
|
elevation: 24,
|
||||||
|
},
|
||||||
|
fullCamera: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 100,
|
||||||
|
},
|
||||||
|
cameraControls: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 120,
|
||||||
|
zIndex: 200,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
controlsRow: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
mainControlsRow: {
|
||||||
|
flex: 0.8,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
switchCameraToggle: {
|
||||||
|
marginRight: 48,
|
||||||
|
},
|
||||||
|
cameraAction: {
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
cameraActionIcon: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
recordingIcon: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
transparentControls: {
|
||||||
|
backgroundColor: '#00000022',
|
||||||
|
},
|
||||||
|
opaqueControls: {
|
||||||
|
backgroundColor: Colors.Black,
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
toggleContainer: {
|
||||||
|
marginTop: 24,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
rewardDriverCard: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: Colors.BrighterLbryGreen,
|
||||||
|
flexDirection: 'row',
|
||||||
|
paddingLeft: 16,
|
||||||
|
paddingRight: 16,
|
||||||
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12,
|
||||||
|
},
|
||||||
|
rewardDriverText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
color: Colors.White,
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
rewardIcon: {
|
||||||
|
color: Colors.White,
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
marginRight: 4,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
tagList: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
textInputLayout: {
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
textInputTitle: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 12,
|
||||||
|
marginBottom: -10,
|
||||||
|
marginLeft: 4,
|
||||||
|
},
|
||||||
|
thumbnailUploadContainer: {
|
||||||
|
marginTop: 16,
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
paddingLeft: 2,
|
||||||
|
paddingRight: 2,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
thumbnailUploadText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
toggleField: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 4,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
toggleText: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default publishStyle;
|
35
app/src/styles/tag.js
Normal file
35
app/src/styles/tag.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
import Colors from './colors';
|
||||||
|
|
||||||
|
const tagStyle = StyleSheet.create({
|
||||||
|
tag: {
|
||||||
|
marginRight: 4,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
tagSearchInput: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingLeft: 8,
|
||||||
|
paddingTop: 4,
|
||||||
|
paddingBottom: 4,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 14,
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
tagResultsList: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tagStyle;
|
|
@ -86,7 +86,7 @@ fullscreen = 0
|
||||||
#android.presplash_color = #FFFFFF
|
#android.presplash_color = #FFFFFF
|
||||||
|
|
||||||
# (list) Permissions
|
# (list) Permissions
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = CAMERA,INTERNET,READ_EXTERNAL_STORAGE,RECORD_AUDIO,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 28
|
android.api = 28
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1, com.google.android.gms:play-services-base:16.1.0, com.android.support:exifinterface:27.1.1
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
|
@ -86,7 +86,7 @@ fullscreen = 0
|
||||||
#android.presplash_color = #FFFFFF
|
#android.presplash_color = #FFFFFF
|
||||||
|
|
||||||
# (list) Permissions
|
# (list) Permissions
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = CAMERA,INTERNET,READ_EXTERNAL_STORAGE,RECORD_AUDIO,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 28
|
android.api = 28
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1, com.google.android.gms:play-services-base:16.1.0, com.android.support:exifinterface:27.1.1
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -86,7 +86,7 @@ fullscreen = 0
|
||||||
#android.presplash_color = #FFFFFF
|
#android.presplash_color = #FFFFFF
|
||||||
|
|
||||||
# (list) Permissions
|
# (list) Permissions
|
||||||
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE
|
android.permissions = CAMERA,INTERNET,READ_EXTERNAL_STORAGE,RECORD_AUDIO,WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 28
|
android.api = 28
|
||||||
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.59.3, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828, com.google.firebase:firebase-core:16.0.1, com.google.android.gms:play-services-base:16.1.0, com.android.support:exifinterface:27.1.1
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
@ -272,3 +272,4 @@ warn_on_root = 1
|
||||||
# Then, invoke the command line with the "demo" profile:
|
# Then, invoke the command line with the "demo" profile:
|
||||||
#
|
#
|
||||||
#buildozer --profile demo android debug
|
#buildozer --profile demo android debug
|
||||||
|
requirements = python3crystax, openssl, sqlite3, hostpython3crystax, android, distro==1.4.0, pyjnius, certifi==2018.11.29, appdirs==1.4.3, docopt==0.6.2, base58==1.0.0, colorama==0.3.7, ecdsa==0.13, jsonschema==2.6.0, pbkdf2==1.3, pyyaml, protobuf==3.6.1, keyring==10.4.0, git+https://github.com/lbryio/aioupnp.git@a404269d91cff5358bcffb8067b0fd1d9c6842d3#egg=aioupnp, asn1crypto, mock, netifaces, cryptography, aiohttp==3.5.4, multidict==4.5.2, yarl==1.3.0, chardet==3.0.4, async_timeout==3.0.1, coincurve, msgpack==0.6.1, six, attrs==18.2.0, pylru, hachoir, "git+https://github.com/lbryio/lbry-sdk@v0.38.0#egg=lbry&subdirectory=lbry", "git+https://github.com/lbryio/lbry-sdk@v0.38.0#egg=torba&subdirectory=torba"
|
|
@ -3,10 +3,11 @@ buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||||
classpath 'com.google.gms:google-services:4.0.1'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +37,8 @@ android {
|
||||||
targetSdkVersion {{ android_api }}
|
targetSdkVersion {{ android_api }}
|
||||||
versionCode {{ args.numeric_version }}
|
versionCode {{ args.numeric_version }}
|
||||||
versionName '{{ args.version }}'
|
versionName '{{ args.version }}'
|
||||||
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
|
multiDexEnabled true
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86"
|
abiFilters "armeabi-v7a", "x86"
|
||||||
|
@ -85,6 +88,8 @@ ext {
|
||||||
minSdkVersion = {{ args.min_sdk_version }}
|
minSdkVersion = {{ args.min_sdk_version }}
|
||||||
targetSdkVersion = {{ android_api }}
|
targetSdkVersion = {{ android_api }}
|
||||||
supportLibVersion = '27.1.1'
|
supportLibVersion = '27.1.1'
|
||||||
|
googlePlayServicesVersion = '16.1.0'
|
||||||
|
googlePlayServicesVisionVersion = '17.0.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
@ -100,8 +105,11 @@ subprojects {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':@react-native-community_async-storage')
|
compile project(':@react-native-community_async-storage')
|
||||||
|
compile project(':react-native-camera')
|
||||||
|
compile project(':react-native-document-picker')
|
||||||
compile project(':react-native-exception-handler')
|
compile project(':react-native-exception-handler')
|
||||||
compile project(':react-native-fast-image')
|
compile project(':react-native-fast-image')
|
||||||
|
compile project(':react-native-fs')
|
||||||
compile project(':react-native-gesture-handler')
|
compile project(':react-native-gesture-handler')
|
||||||
compile project(':react-native-video')
|
compile project(':react-native-video')
|
||||||
compile project(':rn-fetch-blob')
|
compile project(':rn-fetch-blob')
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
rootProject.name = 'browser'
|
rootProject.name = 'browser'
|
||||||
include ':@react-native-community_async-storage'
|
include ':@react-native-community_async-storage'
|
||||||
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, './react/node_modules/@react-native-community/async-storage/android')
|
project(':@react-native-community_async-storage').projectDir = new File(rootProject.projectDir, './react/node_modules/@react-native-community/async-storage/android')
|
||||||
|
include ':react-native-camera'
|
||||||
|
project(':react-native-camera').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-camera/android')
|
||||||
|
include ':react-native-document-picker'
|
||||||
|
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-document-picker/android')
|
||||||
include ':react-native-exception-handler'
|
include ':react-native-exception-handler'
|
||||||
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
|
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
|
||||||
include ':react-native-fast-image'
|
include ':react-native-fast-image'
|
||||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
|
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
|
||||||
|
include ':react-native-fs'
|
||||||
|
project(':react-native-fs').projectDir = new File(settingsDir, './react/node_modules/react-native-fs/android')
|
||||||
include ':react-native-gesture-handler'
|
include ':react-native-gesture-handler'
|
||||||
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-gesture-handler/android')
|
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-gesture-handler/android')
|
||||||
include ':react-native-video'
|
include ':react-native-video'
|
||||||
|
|
3
package-lock.json
generated
Normal file
3
package-lock.json
generated
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package io.lbry.browser;
|
package io.lbry.browser;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -31,9 +32,13 @@ import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.bridge.WritableArray;
|
import com.facebook.react.bridge.WritableArray;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
|
import com.facebook.react.modules.core.PermissionAwareActivity;
|
||||||
|
import com.facebook.react.modules.core.PermissionListener;
|
||||||
import com.facebook.react.shell.MainReactPackage;
|
import com.facebook.react.shell.MainReactPackage;
|
||||||
import com.facebook.react.ReactRootView;
|
import com.facebook.react.ReactRootView;
|
||||||
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
|
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
|
||||||
|
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
|
||||||
|
import com.rnfs.RNFSPackage;
|
||||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||||
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||||
import com.RNFetchBlob.RNFetchBlobPackage;
|
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||||
|
@ -54,8 +59,9 @@ import java.util.Random;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.reactnative.camera.RNCameraPackage;
|
||||||
|
|
||||||
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
|
public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
|
||||||
|
|
||||||
private static Activity currentActivity = null;
|
private static Activity currentActivity = null;
|
||||||
|
|
||||||
|
@ -99,6 +105,8 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
|
|
||||||
private boolean receivedStopService;
|
private boolean receivedStopService;
|
||||||
|
|
||||||
|
private PermissionListener permissionListener;
|
||||||
|
|
||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "LBRYApp";
|
return "LBRYApp";
|
||||||
}
|
}
|
||||||
|
@ -139,8 +147,11 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
.addPackage(new MainReactPackage())
|
.addPackage(new MainReactPackage())
|
||||||
.addPackage(new AsyncStoragePackage())
|
.addPackage(new AsyncStoragePackage())
|
||||||
.addPackage(new FastImageViewPackage())
|
.addPackage(new FastImageViewPackage())
|
||||||
|
.addPackage(new ReactNativeDocumentPicker())
|
||||||
.addPackage(new ReactVideoPackage())
|
.addPackage(new ReactVideoPackage())
|
||||||
|
.addPackage(new RNCameraPackage())
|
||||||
.addPackage(new RNFetchBlobPackage())
|
.addPackage(new RNFetchBlobPackage())
|
||||||
|
.addPackage(new RNFSPackage())
|
||||||
.addPackage(new RNGestureHandlerPackage())
|
.addPackage(new RNGestureHandlerPackage())
|
||||||
.addPackage(new LbryReactPackage())
|
.addPackage(new LbryReactPackage())
|
||||||
.setUseDeveloperSupport(true)
|
.setUseDeveloperSupport(true)
|
||||||
|
@ -352,6 +363,10 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (permissionListener != null) {
|
||||||
|
permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String acquireDeviceId(Context context) {
|
public static String acquireDeviceId(Context context) {
|
||||||
|
@ -473,6 +488,12 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
|
||||||
|
permissionListener = listener;
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
if (mReactInstanceManager != null) {
|
if (mReactInstanceManager != null) {
|
||||||
|
|
320
src/main/java/io/lbry/browser/reactmodules/GalleryModule.java
Normal file
320
src/main/java/io/lbry/browser/reactmodules/GalleryModule.java
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.Manifest;
|
||||||
|
import android.media.ThumbnailUtils;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Arguments;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.WritableArray;
|
||||||
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class GalleryModule extends ReactContextBaseJavaModule {
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
public GalleryModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.context = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Gallery";
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getVideos(Promise promise) {
|
||||||
|
WritableArray items = Arguments.createArray();
|
||||||
|
List<GalleryItem> videos = loadVideos();
|
||||||
|
for (int i = 0; i < videos.size(); i++) {
|
||||||
|
items.pushMap(videos.get(i).toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getThumbnailPath(Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
String thumbnailPath = String.format("%s/thumbnails", cacheDir.getAbsolutePath());
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@ReactMethod
|
||||||
|
public void copyImage(String sourcePath, String destinationPath, Promise promise) {
|
||||||
|
try {
|
||||||
|
File source = new File(sourcePath);
|
||||||
|
File destination = new File(destinationPath);
|
||||||
|
if (source.exists()) {
|
||||||
|
FileChannel src = new FileInputStream(source).getChannel();
|
||||||
|
FileChannel dst = new FileOutputStream(destination).getChannel();
|
||||||
|
dst.transferFrom(src, 0, src.size());
|
||||||
|
src.close();
|
||||||
|
dst.close();
|
||||||
|
|
||||||
|
promise.resolve(true);
|
||||||
|
} else {
|
||||||
|
promise.reject("The source image could not be found. Please try again.");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
promise.reject("The image could not be saved. Please try again.");
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getUploadsPath(Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
String baseFolder = Utils.getExternalStorageDir(context);
|
||||||
|
String uploadsPath = String.format("%s/LBRY/Uploads", baseFolder);
|
||||||
|
File uploadsDir = new File(uploadsPath);
|
||||||
|
if (!uploadsDir.isDirectory()) {
|
||||||
|
uploadsDir.mkdirs();
|
||||||
|
}
|
||||||
|
promise.resolve(uploadsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.reject("The content could not be saved to the device. Please check your storage permissions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createVideoThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
|
||||||
|
File file = new File(thumbnailPath);
|
||||||
|
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the video");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createImageThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
FileOutputStream os = null;
|
||||||
|
try {
|
||||||
|
Bitmap source = BitmapFactory.decodeFile(filePath);
|
||||||
|
// MINI_KIND dimensions
|
||||||
|
Bitmap thumbnail = Bitmap.createScaledBitmap(source, 512, 384, false);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
os = new FileOutputStream(thumbnailPath);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
}
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the image");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (os != null) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
// ignoe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<GalleryItem> loadVideos() {
|
||||||
|
String[] projection = {
|
||||||
|
MediaStore.MediaColumns._ID,
|
||||||
|
MediaStore.MediaColumns.DATA,
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
|
MediaStore.Video.Media.DURATION
|
||||||
|
};
|
||||||
|
|
||||||
|
List<String> ids = new ArrayList<String>();
|
||||||
|
List<GalleryItem> items = new ArrayList<GalleryItem>();
|
||||||
|
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null,
|
||||||
|
String.format("%s DESC", MediaStore.MediaColumns.DATE_MODIFIED));
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
int idColumn = cursor.getColumnIndex(MediaStore.MediaColumns._ID);
|
||||||
|
int nameColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||||
|
int typeColumn = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE);
|
||||||
|
int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
|
||||||
|
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
|
||||||
|
|
||||||
|
String id = cursor.getString(idColumn);
|
||||||
|
GalleryItem item = new GalleryItem();
|
||||||
|
item.setId(id);
|
||||||
|
item.setName(cursor.getString(nameColumn));
|
||||||
|
item.setType(cursor.getString(typeColumn));
|
||||||
|
item.setFilePath(cursor.getString(pathColumn));
|
||||||
|
items.add(item);
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkThumbnails(ids);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkThumbnails(final List<String> ids) {
|
||||||
|
(new AsyncTask<Void, Void, Void>() {
|
||||||
|
protected Void doInBackground(Void... param) {
|
||||||
|
if (context != null) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
for (int i = 0; i < ids.size(); i++) {
|
||||||
|
String id = ids.get(i);
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath()));
|
||||||
|
if (!thumbnailsDir.isDirectory()) {
|
||||||
|
thumbnailsDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id);
|
||||||
|
File file = new File(thumbnailPath);
|
||||||
|
if (!file.exists()) {
|
||||||
|
// save the thumbnail to the path
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = 1;
|
||||||
|
Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail(
|
||||||
|
resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GalleryItem {
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private int duration;
|
||||||
|
|
||||||
|
private String filePath;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDuration(int duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilePath() {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilePath(String filePath) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WritableMap toMap() {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putString("id", id);
|
||||||
|
map.putString("name", name);
|
||||||
|
map.putString("filePath", filePath);
|
||||||
|
map.putString("type", type);
|
||||||
|
map.putInt("duration", duration);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void canUseCamera(final Promise promise) {
|
||||||
|
promise.resolve(MainActivity.hasPermission(Manifest.permission.CAMERA, MainActivity.getActivity()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import io.lbry.browser.reactmodules.BackgroundMediaModule;
|
||||||
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
|
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
|
||||||
import io.lbry.browser.reactmodules.FirstRunModule;
|
import io.lbry.browser.reactmodules.FirstRunModule;
|
||||||
import io.lbry.browser.reactmodules.FirebaseModule;
|
import io.lbry.browser.reactmodules.FirebaseModule;
|
||||||
|
import io.lbry.browser.reactmodules.GalleryModule;
|
||||||
import io.lbry.browser.reactmodules.ScreenOrientationModule;
|
import io.lbry.browser.reactmodules.ScreenOrientationModule;
|
||||||
import io.lbry.browser.reactmodules.VersionInfoModule;
|
import io.lbry.browser.reactmodules.VersionInfoModule;
|
||||||
import io.lbry.browser.reactmodules.UtilityModule;;
|
import io.lbry.browser.reactmodules.UtilityModule;;
|
||||||
|
@ -31,6 +32,7 @@ public class LbryReactPackage implements ReactPackage {
|
||||||
modules.add(new DaemonServiceControlModule(reactContext));
|
modules.add(new DaemonServiceControlModule(reactContext));
|
||||||
modules.add(new FirstRunModule(reactContext));
|
modules.add(new FirstRunModule(reactContext));
|
||||||
modules.add(new FirebaseModule(reactContext));
|
modules.add(new FirebaseModule(reactContext));
|
||||||
|
modules.add(new GalleryModule(reactContext));
|
||||||
modules.add(new ScreenOrientationModule(reactContext));
|
modules.add(new ScreenOrientationModule(reactContext));
|
||||||
modules.add(new UtilityModule(reactContext));
|
modules.add(new UtilityModule(reactContext));
|
||||||
modules.add(new VersionInfoModule(reactContext));
|
modules.add(new VersionInfoModule(reactContext));
|
||||||
|
|
|
@ -12,7 +12,6 @@ from lbry.extras.daemon.loggly_handler import get_loggly_handler
|
||||||
from lbry.extras.daemon.Components import DHT_COMPONENT, HASH_ANNOUNCER_COMPONENT, PEER_PROTOCOL_SERVER_COMPONENT
|
from lbry.extras.daemon.Components import DHT_COMPONENT, HASH_ANNOUNCER_COMPONENT, PEER_PROTOCOL_SERVER_COMPONENT
|
||||||
from lbry.extras.daemon.Daemon import Daemon
|
from lbry.extras.daemon.Daemon import Daemon
|
||||||
from lbry.extras.daemon.loggly_handler import get_loggly_handler
|
from lbry.extras.daemon.loggly_handler import get_loggly_handler
|
||||||
from lbry.utils import check_connection
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.setLevel(logging.DEBUG)
|
log.setLevel(logging.DEBUG)
|
||||||
|
|
Loading…
Reference in a new issue