diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff1e01ed..5e30e8da 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 - rm ~/.buildozer/android/crystax-ndk-10.3.2-linux-x86_64.tar.xz - 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" - cp $CI_PROJECT_DIR/bin/browser-$BUILD_VERSION-release.apk /dev/null diff --git a/app/package-lock.json b/app/package-lock.json index 58b667d0..ecdde671 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -4881,6 +4881,11 @@ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "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": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -5553,8 +5558,8 @@ } }, "lbry-redux": { - "version": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3", - "from": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3", + "version": "github:lbryio/lbry-redux#9a676ee311d573b84d11f402d918aeee77be76e1", + "from": "github:lbryio/lbry-redux", "requires": { "proxy-polyfill": "0.1.6", "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": { "version": "0.6.2", "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": { "version": "2.9.0", "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", "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": { "version": "1.2.1", "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" } }, + "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": { "version": "1.4.1", "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", "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": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/app/package.json b/app/package.json index 5326fd67..a488a898 100644 --- a/app/package.json +++ b/app/package.json @@ -10,7 +10,8 @@ "dependencies": { "base-64": "^0.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", "lodash": ">=4.17.11", "merge": ">=1.2.1", @@ -18,13 +19,17 @@ "react": "16.8.6", "react-native": "0.59.3", "@react-native-community/async-storage": "^1.2.2", + "react-native-camera": "^2.11.0", "react-native-country-picker-modal": "^0.6.2", + "react-native-document-picker": "^2.3.0", "react-native-exception-handler": "2.9.0", "react-native-fast-image": "^5.0.3", + "react-native-fs": "^2.13.3", "react-native-gesture-handler": "^1.1.0", "react-native-image-zoom-viewer": "^2.2.5", "react-native-password-strength-meter": "^0.0.2", "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-video": "lbryio/react-native-video#exoplayer-lbry-android", "react-navigation": "^3.11.0", diff --git a/app/src/component/AppNavigator.js b/app/src/component/AppNavigator.js index ff35fcf0..74d3ddcb 100644 --- a/app/src/component/AppNavigator.js +++ b/app/src/component/AppNavigator.js @@ -5,6 +5,7 @@ import DownloadsPage from 'page/downloads'; import DrawerContent from 'component/drawerContent'; import FilePage from 'page/file'; import FirstRunScreen from 'page/firstRun'; +import PublishPage from 'page/publish'; import RewardsPage from 'page/rewards'; import TrendingPage from 'page/trending'; import SearchPage from 'page/search'; @@ -148,6 +149,12 @@ const drawer = createDrawerNavigator( drawerIcon: ({ tintColor }) => , }, }, + Publish: { + screen: PublishPage, + navigationOptions: { + drawerIcon: ({ tintColor }) => , + }, + }, Rewards: { screen: RewardsPage, navigationOptions: { diff --git a/app/src/component/channelSelector/index.js b/app/src/component/channelSelector/index.js new file mode 100644 index 00000000..daee8c76 --- /dev/null +++ b/app/src/component/channelSelector/index.js @@ -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); diff --git a/app/src/component/channelSelector/view.js b/app/src/component/channelSelector/view.js new file mode 100644 index 00000000..b93d6514 --- /dev/null +++ b/app/src/component/channelSelector/view.js @@ -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 ( + + + {pickerItems.map(item => ( + + ))} + + + {this.state.showCreateChannel && ( + + + @ + + + + + Deposit + + LBC + + + This LBC remains yours. It is a deposit to reserve the name and can be undone at any time. + + + + {creatingChannel && } + {!creatingChannel && ( + + +