implement phone verification (#274)

* implement phone verification
* update permissions in buildozer.spec
This commit is contained in:
Akinwale Ariwodola 2018-09-03 02:57:54 +01:00 committed by GitHub
parent e8185e0f9c
commit c0b464ae36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
260 changed files with 534 additions and 155 deletions

92
app/package-lock.json generated
View file

@ -3360,6 +3360,11 @@
}
}
},
"fuse.js": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-2.6.2.tgz",
"integrity": "sha1-1dmU/alvVDtaUd84tyzsnMYNneo="
},
"gauge": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz",
@ -3431,6 +3436,11 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
},
"google-libphonenumber": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-2.0.19.tgz",
"integrity": "sha512-kwtbruT+eyiof081cxT1tltMTxgTOq3CQhUoEYBROC+vNf+COPqzfKJtVnDvgXQe4SzfbnAYkP8KoSpbJBIlSg=="
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@ -3980,21 +3990,11 @@
}
},
"lbryinc": {
"version": "github:lbryio/lbryinc#678c5098e2099dd1560b2fefa2795f38ca3ce07b",
"from": "github:lbryio/lbryinc",
"version": "github:lbryio/lbryinc#7910b565d7edda16be1c9d291f296982261ba60a",
"from": "github:lbryio/lbryinc#phone-verification",
"requires": {
"lbry-redux": "github:lbryio/lbry-redux#421321a78397251589e5a890f4caa95e79975e2b",
"lbry-redux": "github:lbryio/lbry-redux#31f7afa8a37f5741dac01fc1ecdf153f3bed95dc",
"reselect": "^3.0.0"
},
"dependencies": {
"lbry-redux": {
"version": "github:lbryio/lbry-redux#421321a78397251589e5a890f4caa95e79975e2b",
"from": "github:lbryio/lbry-redux",
"requires": {
"proxy-polyfill": "0.1.6",
"reselect": "^3.0.0"
}
}
}
},
"lcid": {
@ -4098,6 +4098,11 @@
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
},
"lodash.unset": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.5.2.tgz",
@ -4564,6 +4569,14 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"node-emoji": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz",
"integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==",
"requires": {
"lodash.toarray": "^4.4.0"
}
},
"node-fetch": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
@ -5200,6 +5213,44 @@
}
}
},
"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",
"integrity": "sha1-upcRi+Q3O+DBHNUeRF5r1Eji8co=",
"requires": {
"fuse.js": "2.6.2",
"lodash": "4.12.0",
"node-emoji": "1.8.1",
"prop-types": "15.6.0",
"react-native-safe-area-view": "^0.7.0",
"world-countries": "1.8.0"
},
"dependencies": {
"lodash": {
"version": "4.12.0",
"resolved": "http://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz",
"integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg="
},
"prop-types": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz",
"integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=",
"requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"react-native-safe-area-view": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.7.0.tgz",
"integrity": "sha512-SjLdW/Th0WVMhyngH4O6yC21S+O4U4AAG3QxBr7fZ2ftgjXSpKbDHAhEpxBdFwei6HsnsC2h9oYMtPpaW9nfGg==",
"requires": {
"hoist-non-react-statics": "^2.3.1"
}
}
}
},
"react-native-dismiss-keyboard": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-native-dismiss-keyboard/-/react-native-dismiss-keyboard-1.0.0.tgz",
@ -5263,6 +5314,16 @@
"react-native-image-pan-zoom": "^2.1.2"
}
},
"react-native-phone-input": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/react-native-phone-input/-/react-native-phone-input-0.2.1.tgz",
"integrity": "sha1-rGhSoeo32NWP+D3tUtGNe2MD5mc=",
"requires": {
"google-libphonenumber": "^2.0.9",
"lodash": "^4.17.4",
"prop-types": "^15.5.10"
}
},
"react-native-safe-area-view": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.9.0.tgz",
@ -6837,6 +6898,11 @@
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"world-countries": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/world-countries/-/world-countries-1.8.0.tgz",
"integrity": "sha1-F/SOfoRwrFohNq1pON/GVvwry5U="
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View file

@ -8,13 +8,15 @@
"dependencies": {
"base-64": "^0.1.0",
"lbry-redux": "lbryio/lbry-redux",
"lbryinc": "lbryio/lbryinc",
"lbryinc": "lbryio/lbryinc#phone-verification",
"moment": "^2.22.1",
"react": "16.2.0",
"react-native": "0.55.3",
"react-native-country-picker-modal": "^0.6.2",
"react-native-fast-image": "^5.0.3",
"react-native-fetch-blob": "^0.10.8",
"react-native-image-zoom-viewer": "^2.2.5",
"react-native-phone-input": "^0.2.1",
"react-native-vector-icons": "^5.0.0",
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
"react-navigation": "^2.12.1",

View file

@ -268,7 +268,7 @@ class AppWithNavigationState extends React.Component {
}
if ('toast' === currentDisplayType) {
ToastAndroid.show(message, ToastAndroid.SHORT);
ToastAndroid.show(message, ToastAndroid.LONG);
}
dispatch(doHideNotification());

View file

@ -1,9 +0,0 @@
import { connect } from 'react-redux';
import { doNotify } from 'lbry-redux';
import DeviceIdRewardSubcard from './view';
const perform = dispatch => ({
notify: data => dispatch(doNotify(data))
});
export default connect(null, perform)(DeviceIdRewardSubcard);

View file

@ -1,48 +0,0 @@
// @flow
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
NativeModules,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Button from '../button';
import Colors from '../../styles/colors';
import Constants from '../../constants';
import Link from '../link';
import rewardStyle from '../../styles/reward';
class DeviceIdRewardSubcard extends React.PureComponent {
onAllowAccessPressed = () => {
if (!NativeModules.UtilityModule) {
return notify({
message: 'The device ID could not be obtained due to a missing module.',
displayType: ['toast']
});
}
NativeModules.UtilityModule.requestPhoneStatePermission();
}
render() {
return (
<View style={rewardStyle.subcard}>
<Text style={rewardStyle.subtitle}>Pending action: Device ID</Text>
<Text style={[rewardStyle.bottomMarginMedium, rewardStyle.subcardText]}>
The app requires the phone state permission in order to identify your device for reward eligibility.
</Text>
<Button
style={rewardStyle.actionButton}
text={"Allow Access"}
onPress={this.onAllowAccessPressed}
/>
</View>
);
}
};
export default DeviceIdRewardSubcard;

View file

@ -86,9 +86,10 @@ class EmailRewardSubcard extends React.PureComponent {
value={this.state.email}
onChangeText={text => this.handleChangeText(text)} />
{!this.state.verifyStarted && <Button style={rewardStyle.actionButton}
text={"Send Verification Email"}
text={"Send verification email"}
onPress={this.onSendVerificationPressed} />}
{this.state.verifyStarted && emailNewPending && <ActivityIndicator size={"small"} color={Colors.LbryGreen} />}
{this.state.verifyStarted && emailNewPending &&
<ActivityIndicator size={"small"} color={Colors.LbryGreen} style={rewardStyle.loading} />}
</View>
);
}

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import { doNotify } from 'lbry-redux';
import {
doUserPhoneNew,
doUserPhoneVerify,
selectPhoneNewErrorMessage,
selectPhoneNewIsPending,
selectPhoneToVerify,
selectPhoneVerifyIsPending,
selectPhoneVerifyErrorMessage
} from 'lbryinc';
import PhoneNumberRewardSubcard from './view';
const select = state => ({
phoneVerifyErrorMessage: selectPhoneVerifyErrorMessage(state),
phoneVerifyIsPending: selectPhoneVerifyIsPending(state),
phone: selectPhoneToVerify(state),
phoneNewErrorMessage: selectPhoneNewErrorMessage(state),
phoneNewIsPending: selectPhoneNewIsPending(state),
});
const perform = dispatch => ({
addUserPhone: (phone, country_code) => dispatch(doUserPhoneNew(phone, country_code)),
verifyPhone: (verificationCode) => dispatch(doUserPhoneVerify(verificationCode)),
notify: data => dispatch(doNotify(data)),
});
export default connect(select, perform)(PhoneNumberRewardSubcard);

View file

@ -0,0 +1,253 @@
// @flow
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
DeviceEventEmitter,
NativeModules,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import Button from '../button';
import Colors from '../../styles/colors';
import Constants from '../../constants';
import CountryPicker from 'react-native-country-picker-modal';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from '../link';
import PhoneInput from 'react-native-phone-input';
import rewardStyle from '../../styles/reward';
class PhoneNumberRewardSubcard extends React.PureComponent {
phoneInput = null;
picker = null;
constructor(props) {
super(props);
this.state = {
canReceiveSms: false,
cca2: 'US',
codeVerifyStarted: false,
codeVerifySuccessful: false,
countryCode: null,
newPhoneAdded: false,
number: null,
verificationCode: null,
};
}
componentDidMount() {
DeviceEventEmitter.addListener('onReceiveSmsPermissionGranted', this.receiveSmsPermissionGranted);
DeviceEventEmitter.addListener('onVerificationCodeReceived', this.receiveVerificationCode);
const { phone } = this.props;
if (phone && String(phone).trim().length() > 0) {
this.setState({ newPhoneAdded: true });
}
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.canReceiveSms().then(canReceiveSms => this.setState({ canReceiveSms }));
}
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('onReceiveSmsPermissionGranted', this.receiveSmsPermissionGranted);
DeviceEventEmitter.removeListener('onVerificationCodeReceived', this.receiveVerificationCode);
}
componentDidUpdate(prevProps) {
const {
phoneVerifyIsPending,
phoneVerifyErrorMessage,
notify,
phoneNewErrorMessage,
phoneNewIsPending,
onPhoneVerifySuccessful
} = this.props;
if (!phoneNewIsPending && (phoneNewIsPending !== prevProps.phoneNewIsPending)) {
if (phoneNewErrorMessage) {
notify({ message: String(phoneNewErrorMessage), displayType: ['toast'] });
} else {
this.setState({ newPhoneAdded: true });
}
}
if (!phoneVerifyIsPending && (phoneVerifyIsPending !== prevProps.phoneVerifyIsPending)) {
if (phoneVerifyErrorMessage) {
notify({ message: String(phoneVerifyErrorMessage), displayType: ['toast'] });
this.setState({ codeVerifyStarted: false });
} else {
notify({ message: 'Your phone number was successfully verified.', displayType: ['toast'] });
this.setState({ codeVerifySuccessful: true });
if (onPhoneVerifySuccessful) {
onPhoneVerifySuccessful();
}
}
}
}
receiveSmsPermissionGranted = () => {
this.setState({ canReceiveSms: true });
}
receiveVerificationCode = (evt) => {
if (!this.state.newPhoneAdded || this.state.codeVerifySuccessful) {
return;
}
const { verifyPhone } = this.props;
this.setState({ codeVerifyStarted: true });
verifyPhone(evt.code);
}
onAllowAccessPressed = () => {
if (!NativeModules.UtilityModule) {
return notify({
message: 'The required permission could not be obtained due to a missing module.',
displayType: ['toast']
});
}
NativeModules.UtilityModule.requestReceiveSmsPermission();
}
onSendTextPressed = () => {
const { addUserPhone, notify } = this.props;
if (!this.phoneInput.isValidNumber()) {
return notify({
message: 'Please provide a valid telephone number.',
displayType: ['toast']
});
}
const countryCode = this.phoneInput.getCountryCode();
const number = this.phoneInput.getValue().replace('+' + countryCode, '');
this.setState({ countryCode, number });
addUserPhone(number, countryCode);
}
onVerifyPressed = () => {
if (this.state.codeVerifyStarted) {
return;
}
const { verifyPhone } = this.props;
this.setState({ codeVerifyStarted: true });
verifyPhone(this.state.verificationCode);
}
onPressFlag = () => {
if (this.picker) {
this.picker.openModal();
}
}
selectCountry(country) {
this.phoneInput.selectCountry(country.cca2.toLowerCase());
this.setState({ cca2: country.cca2 });
}
handleChangeText = (text) => {
this.setState({ verificationCode: text });
};
render() {
const {
phoneVerifyIsPending,
phoneVerifyErrorMessage,
phone,
phoneErrorMessage,
phoneNewIsPending
} = this.props;
if (this.state.codeVerifySuccessful) {
return null;
}
return (
<View style={rewardStyle.subcard}>
<Text style={rewardStyle.subtitle}>Pending action: Verify Phone Number</Text>
{!this.state.canReceiveSms &&
<View style={rewardStyle.smsPermissionContainer}>
<Text style={[rewardStyle.bottomMarginMedium, rewardStyle.subcardText]}>
You can grant access to the receive SMS permission in order to verify phone number. Alternatively, you can enter the verification code manually.
</Text>
<Button
style={rewardStyle.actionButton}
text={"Allow access"}
onPress={this.onAllowAccessPressed}
/>
</View>}
<View style={rewardStyle.phoneVerificationContainer}>
{!this.state.newPhoneAdded &&
<View>
<Text style={[rewardStyle.bottomMarginMedium, rewardStyle.subcardText]}>Please enter your phone number to continue.</Text>
<PhoneInput
ref={(ref) => { this.phoneInput = ref; }}
style={StyleSheet.flatten(rewardStyle.phoneInput)}
textProps={{ placeholder: '(phone number)' }}
textStyle={StyleSheet.flatten(rewardStyle.phoneInputText)}
onPressFlag={this.onPressFlag} />
{!phoneNewIsPending &&
<Button
style={[rewardStyle.actionButton, rewardStyle.topMarginMedium]}
text={"Send verification text"}
onPress={this.onSendTextPressed} />}
{phoneNewIsPending &&
<ActivityIndicator
style={[rewardStyle.loading, rewardStyle.topMarginMedium]}
size="small"
color={Colors.LbryGreen} />}
</View>}
{this.state.newPhoneAdded &&
<View>
{!phoneVerifyIsPending && !this.codeVerifyStarted &&
<View>
<Text style={[rewardStyle.bottomMarginSmall, rewardStyle.subcardText]}>
Please enter the verification code.
</Text>
<TextInput
style={rewardStyle.verificationCodeInput}
keyboardType="numeric"
placeholder="0000"
underlineColorAndroid="transparent"
value={this.state.verificationCode}
onChangeText={text => this.handleChangeText(text)}
/>
<Button
style={[rewardStyle.actionButton, rewardStyle.topMarginSmall ]}
text={"Verify"}
onPress={this.onVerifyPressed} />
</View>
}
{phoneVerifyIsPending &&
<View>
<Text style={rewardStyle.subcardText}>Verifying your phone number...</Text>
<ActivityIndicator
color={Colors.LbryGreen}
size="small"
style={[rewardStyle.loading, rewardStyle.topMarginMedium]} />
</View>}
</View>
}
</View>
<CountryPicker
ref={(picker) => { this.picker = picker; }}
cca2={this.state.cca2}
filterable={true}
onChange={value => this.selectCountry(value)}
showCallingCode={true}
translation="eng">
<View />
</CountryPicker>
</View>
);
}
};
export default PhoneNumberRewardSubcard;

View file

@ -13,24 +13,14 @@ class RewardSummary extends React.Component {
const { user } = this.props;
let actionsLeft = 0;
if (!user || !user.has_verified_email) {
actionsLeft++;
actionsLeft++;
}
this.setState({ actionsLeft }, () => {
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.canAcquireDeviceId().then(canAcquire => {
if (!canAcquire) {
this.setState({ actionsLeft: this.state.actionsLeft + 1 });
return;
}
}).catch(err => {
this.setState({ actionsLeft: this.state.actionsLeft + 1 });
});
} else {
// unable to retrieve device ID because the native module is not present.
this.setState({ actionsLeft: this.state.actionsLeft + 1 });
}
});
if (!user || !user.is_identity_verified) {
actionsLeft++;
}
this.setState({ actionsLeft });
}
render() {

View file

@ -1,7 +1,6 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
DeviceEventEmitter,
ActivityIndicator,
NativeModules,
ScrollView,
@ -11,7 +10,7 @@ import {
import { doInstallNew } from 'lbryinc';
import Colors from '../../styles/colors';
import Link from '../../component/link';
import DeviceIdRewardSubcard from '../../component/deviceIdRewardSubcard';
import PhoneNumberRewardSubcard from '../../component/phoneNumberRewardSubcard';
import EmailRewardSubcard from '../../component/emailRewardSubcard';
import PageHeader from '../../component/pageHeader';
import RewardCard from '../../component/rewardCard';
@ -19,33 +18,21 @@ import rewardStyle from '../../styles/reward';
class RewardsPage extends React.PureComponent {
state = {
canAcquireDeviceId: false,
isEmailVerified: false,
isIdentityVerified: false,
isRewardApproved: false,
verifyRequestStarted: false,
};
componentDidMount() {
DeviceEventEmitter.addListener('onPhoneStatePermissionGranted', this.phoneStatePermissionGranted);
this.props.fetchRewards();
const { user } = this.props;
this.setState({
isEmailVerified: (user && user.primary_email && user.has_verified_email),
isIdentityVerified: (user && user.is_identity_verified),
isRewardApproved: (user && user.is_reward_approved)
});
if (NativeModules.UtilityModule) {
const util = NativeModules.UtilityModule;
util.canAcquireDeviceId().then(canAcquireDeviceId => {
this.setState({ canAcquireDeviceId });
});
}
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('onPhoneStatePermissionGranted', this.phoneStatePermissionGranted);
}
componentWillReceiveProps(nextProps) {
@ -67,13 +54,24 @@ class RewardsPage extends React.PureComponent {
}
renderVerification() {
if (!this.state.isRewardApproved) {
if (!this.state.isEmailVerified || !this.state.isIdentityVerified) {
return (
<View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Humans Only</Text>
<Text style={rewardStyle.text}>Rewards are for human beings only. You'll have to prove you're one of us before you can claim any rewards.</Text>
{!this.state.canAcquireDeviceId && <DeviceIdRewardSubcard />}
{!this.state.isEmailVerified && <EmailRewardSubcard />}
{!this.state.isIdentityVerified && <PhoneNumberRewardSubcard />}
</View>
);
}
if (this.state.isEmailVerified && this.state.isIdentityVerified && !this.state.isRewardApproved) {
return (
<View style={[rewardStyle.card, rewardStyle.verification]}>
<Text style={rewardStyle.title}>Manual Reward Verification</Text>
<Text style={rewardStyle.text}>
You need to be manually verified before you can start claiming rewards. Please request to be verified on the <Link style={rewardStyle.textLink} href="https://discordapp.com/invite/Z3bERWA" text="LBRY Discord server" />.
</Text>
</View>
);
}
@ -81,28 +79,6 @@ class RewardsPage extends React.PureComponent {
return null;
}
phoneStatePermissionGranted = () => {
const { install, notify } = this.props;
if (NativeModules.UtilityModule) {
const util = NativeModules.UtilityModule;
// Double-check just to be sure
util.canAcquireDeviceId().then(canAcquireDeviceId => {
this.setState({ canAcquireDeviceId });
if (canAcquireDeviceId) {
util.getDeviceId(false).then(deviceId => {
NativeModules.VersionInfo.getAppVersion().then(appVersion => {
doInstallNew(`android-${appVersion}`, deviceId);
});
}).catch((error) => {
notify({ message: error, displayType: ['toast'] });
this.setState({ canAcquireDeviceId: false });
});
}
});
}
}
renderUnclaimedRewards() {
const { claimed, fetching, rewards, user } = this.props;
@ -123,7 +99,8 @@ class RewardsPage extends React.PureComponent {
return (
<View style={rewardStyle.busyContainer}>
<Text style={rewardStyle.infoText}>
{(claimed && claimed.length) ? "You have claimed all available rewards! We're regularly adding more so be sure to check back later." :
{(claimed && claimed.length) ?
"You have claimed all available rewards! We're regularly adding more so be sure to check back later." :
"There are no rewards available at this time, please check back later."}
</Text>
</View>
@ -157,13 +134,11 @@ class RewardsPage extends React.PureComponent {
return (
<View style={rewardStyle.container}>
{this.renderVerification()}
<View style={rewardStyle.rewardsContainer}>
<ScrollView style={rewardStyle.scrollContainer} contentContainerStyle={rewardStyle.scrollContentContainer}>
{this.renderUnclaimedRewards()}
{this.renderClaimedRewards()}
</ScrollView>
</View>
<ScrollView style={rewardStyle.scrollContainer} contentContainerStyle={rewardStyle.scrollContentContainer}>
{this.renderVerification()}
{this.renderUnclaimedRewards()}
{this.renderClaimedRewards()}
</ScrollView>
</View>
);
}

View file

@ -82,6 +82,12 @@ const rewardStyle = StyleSheet.create({
marginTop: 2,
marginBottom: 2
},
topMarginSmall: {
marginTop: 8
},
topMarginMedium: {
marginTop: 16
},
bottomMarginSmall: {
marginBottom: 8
},
@ -96,6 +102,9 @@ const rewardStyle = StyleSheet.create({
fontFamily: 'Metropolis-Regular',
fontSize: 14,
},
textLink: {
color: Colors.LbryGreen
},
leftCol: {
width: '15%',
alignItems: 'center',
@ -153,6 +162,35 @@ const rewardStyle = StyleSheet.create({
fontFamily: 'Metropolis-Regular',
fontSize: 14,
lineHeight: 22
},
phoneVerificationContainer: {
paddingLeft: 4,
paddingRight: 4
},
phoneInput: {
marginLeft: 8
},
phoneInputText: {
fontFamily: 'Metropolis-Regular',
fontSize: 16,
letterSpacing: 1.3
},
verifyingText: {
fontFamily: 'Metropolis-Regular',
fontSize: 14,
marginLeft: 12,
alignSelf: 'flex-start'
},
verificationCodeInput: {
fontFamily: 'Metropolis-Regular',
fontSize: 24,
letterSpacing: 12
},
loading: {
alignSelf: 'flex-start'
},
smsPermissionContainer: {
marginBottom: 32
}
});

View file

@ -86,7 +86,7 @@ fullscreen = 0
#android.presplash_color = #FFFFFF
# (list) Permissions
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,READ_PHONE_STATE,WRITE_EXTERNAL_STORAGE
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,RECEIVE_SMS,WRITE_EXTERNAL_STORAGE
# (int) Android API to use
android.api = 27

View file

@ -86,7 +86,7 @@ fullscreen = 0
#android.presplash_color = #FFFFFF
# (list) Permissions
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,READ_PHONE_STATE,WRITE_EXTERNAL_STORAGE
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,RECEIVE_SMS,WRITE_EXTERNAL_STORAGE
# (int) Android API to use
android.api = 27

View file

@ -86,7 +86,7 @@ fullscreen = 0
#android.presplash_color = #FFFFFF
# (list) Permissions
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,READ_PHONE_STATE,WRITE_EXTERNAL_STORAGE
android.permissions = ACCESS_NETWORK_STATE,BLUETOOTH,INTERNET,READ_EXTERNAL_STORAGE,RECEIVE_SMS,WRITE_EXTERNAL_STORAGE
# (int) Android API to use
android.api = 27

View file

@ -8,10 +8,10 @@ jarsigner -verbose -sigalg SHA1withRSA \
-digestalg SHA1 \
-keystore lbry-android.keystore \
-storepass $KEYSTORE_PASSWORD \
bin/browser-$version-release-unsigned.apk lbry-android \
bin/browser-$version-release-unsigned.apk lbry-android > /dev/null \
&& mv bin/browser-$version-release-unsigned.apk bin/browser-$version-release-signed.apk
~/.buildozer/android/platform/android-sdk-23/build-tools/26.0.1/zipalign -v 4 \
bin/browser-$version-release-signed.apk bin/browser-$version-release.apk \
bin/browser-$version-release-signed.apk bin/browser-$version-release.apk > /dev/null \
&& rm bin/browser-$version-release-signed.apk

View file

@ -16,6 +16,7 @@ import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.widget.Toast;
@ -25,7 +26,9 @@ import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.shell.MainReactPackage;
import com.RNFetchBlob.RNFetchBlobPackage;
@ -52,10 +55,14 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
private static final int PHONE_STATE_PERMISSION_REQ_CODE = 202;
private BroadcastReceiver stopServiceReceiver;
private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203;
private BroadcastReceiver backgroundMediaReceiver;
private BroadcastReceiver smsReceiver;
private BroadcastReceiver stopServiceReceiver;
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@ -96,6 +103,9 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
// Register the stop service receiver (so that we close the activity if the user requests the service to stop)
registerStopReceiver();
// Register SMS receiver for handling verification texts
registerSmsReceiver();
// Start the daemon service if it is not started
serviceRunning = isServiceRunning(LbrynetService.class);
if (!serviceRunning) {
@ -159,6 +169,48 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
registerReceiver(backgroundMediaReceiver, backgroundMediaFilter);
}
public void registerSmsReceiver() {
if (!hasPermission(Manifest.permission.RECEIVE_SMS, this)) {
// don't create the receiver if we don't have the read sms permission
return;
}
IntentFilter smsFilter = new IntentFilter();
smsFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
smsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Get the message
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
if (pdus != null && pdus.length > 0) {
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdus[0]);
String text = sms.getMessageBody();
if (text == null || text.trim().length() == 0) {
return;
}
// Retrieve verification code from the text message if it contains
// the strings "lbry", "verification code" and the colon (following the expected format)
text = text.toLowerCase();
if (text.indexOf("lbry") > -1 && text.indexOf("verification code") > -1 && text.indexOf(":") > -1) {
String code = text.substring(text.lastIndexOf(":") + 1).trim();
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
WritableMap params = Arguments.createMap();
params.putString("code", code);
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onVerificationCodeReceived", params);
}
}
}
}
}
};
registerReceiver(smsReceiver, smsFilter);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
@ -212,6 +264,27 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
"No permission granted to read your device state. Rewards cannot be claimed.", Toast.LENGTH_LONG).show();
}
break;
case RECEIVE_SMS_PERMISSION_REQ_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted. Emit an onPhoneStatePermissionGranted event
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onReceiveSmsPermissionGranted", null);
}
// register the receiver
if (smsReceiver == null) {
registerSmsReceiver();
}
} else {
// Permission not granted. Simply show a message.
Toast.makeText(this,
"No permission granted to receive your SMS messages. You may have to enter the verification code manually.",
Toast.LENGTH_LONG).show();
}
break;
}
}
@ -292,6 +365,11 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
backgroundMediaReceiver = null;
}
if (smsReceiver != null) {
unregisterReceiver(smsReceiver);
smsReceiver = null;
}
if (stopServiceReceiver != null) {
unregisterReceiver(stopServiceReceiver);
stopServiceReceiver = null;
@ -358,6 +436,15 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
true);
}
public static void checkReceiveSmsPermission(Context context) {
// Request read phone state permission
checkPermission(Manifest.permission.RECEIVE_SMS,
RECEIVE_SMS_PERMISSION_REQ_CODE,
"LBRY requires access to be able to read a verification text message for rewards.",
context,
true);
}
private boolean isServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {

View file

@ -109,9 +109,9 @@ public class UtilityModule extends ReactContextBaseJavaModule {
}
} catch (SecurityException ex) {
// Maybe the permission was not granted? Try to acquire permission
if (requestPermission) {
/*if (requestPermission) {
requestPhoneStatePermission();
}
}*/
} catch (Exception ex) {
// id could not be obtained. Display a warning that rewards cannot be claimed.
promise.reject(ex.getMessage());
@ -126,20 +126,16 @@ public class UtilityModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void canAcquireDeviceId(final Promise promise) {
if (isEmulator()) {
promise.resolve(false);
}
promise.resolve(MainActivity.hasPermission(Manifest.permission.READ_PHONE_STATE, MainActivity.getActivity()));
public void canReceiveSms(final Promise promise) {
promise.resolve(MainActivity.hasPermission(Manifest.permission.RECEIVE_SMS, MainActivity.getActivity()));
}
@ReactMethod
public void requestPhoneStatePermission() {
public void requestReceiveSmsPermission() {
MainActivity activity = (MainActivity) MainActivity.getActivity();
if (activity != null) {
// Request for the READ_PHONE_STATE permission
MainActivity.checkPhoneStatePermission(activity);
// Request for the RECEIVE_SMS permission
MainActivity.checkReceiveSmsPermission(activity);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Some files were not shown because too many files have changed in this diff Show more