...
 
Commits (23)
......@@ -186,10 +186,14 @@ export default class App extends Component<Props, State> {
}
/**
* On component will mount
* contructor
*/
componentWillMount() {
if (!Text.defaultProps) Text.defaultProps = {};
constructor(props) {
super(props);
if (!Text.defaultProps) {
Text.defaultProps = {};
}
Text.defaultProps.style = {
fontFamily: 'Roboto',
color: '#444',
......
......@@ -28,9 +28,13 @@
- iOS
- Android
## Building
## Install dependencies
- `yarn install`
- `cd ios && pod install` (iOS only)
## Building
- `yarn android` or `yarn ios`
## Testing
......
......@@ -9,6 +9,7 @@ import {
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
// mock backhandler
......
import 'react-native';
import React from 'react';
import { shallow } from 'enzyme';
import { Button } from 'react-native-elements';
import ForgotPassword from '../../src/auth/ForgotPassword';
import authService from '../../src/auth/AuthService';
jest.mock('../../src/auth/AuthService');
// Note: test renderer must be required after react-native.
......@@ -37,7 +37,7 @@ describe('ForgotPassword component', () => {
});
// press send
await render.find('Button').at(1).simulate('press');
await render.find(Button).at(1).simulate('press');
// expect auth service login to be called once
expect(authService.forgot).toBeCalled();
......@@ -57,7 +57,7 @@ describe('ForgotPassword component', () => {
const render = wrapper.dive();
// press go back
await render.find('Button').at(0).simulate('press');
await render.find(Button).at(0).simulate('press');
// expect onLogin to be called once
expect(mockFn).toBeCalled();
......
......@@ -2,6 +2,7 @@ import 'react-native';
import React from 'react';
import { Text, TouchableOpacity } from "react-native";
import { shallow } from 'enzyme';
import { Button } from 'react-native-elements';
import LoginForm from '../../src/auth/LoginForm';
import authService from '../../src/auth/AuthService';
......@@ -41,7 +42,7 @@ describe('LoginForm component', () => {
});
// press login
await render.find('Button').at(1).simulate('press');
await render.find(Button).at(1).simulate('press');
// check state
expect(wrapper.state().password).toEqual('data');
......
......@@ -2,6 +2,7 @@ import 'react-native';
import React from 'react';
import { Alert } from "react-native";
import { shallow } from 'enzyme';
import { Button, CheckBox } from 'react-native-elements';
import RegisterForm from '../../src/auth/RegisterForm';
import authService from '../../src/auth/AuthService';
......@@ -45,7 +46,7 @@ describe('RegisterForm component', () => {
let inputs = render.find('TextInput');
// should have 4 inputs
expect(inputs.length).toBe(3);
expect(inputs.length).toBe(4);
// simulate user input
inputs.at(0).simulate('changeText', 'myFancyUsername');
......@@ -53,7 +54,7 @@ describe('RegisterForm component', () => {
inputs.at(2).simulate('changeText', 'somepassword');
// simulate press checkbox
await render.find('CheckBox').at(1).simulate('press');
await render.find(CheckBox).at(1).simulate('press');
// update component (password confirmation is shown after the password field is set)
await wrapper.update();
......@@ -65,7 +66,7 @@ describe('RegisterForm component', () => {
inputs.at(3).simulate('changeText', 'somepassword');
// simulate press register
await render.find('Button').at(1).simulate('press');
await render.find(Button).at(1).simulate('press');
// expect auth service register to be called once
expect(authService.register).toBeCalled();
......@@ -83,8 +84,7 @@ describe('RegisterForm component', () => {
// find the text inputs
let inputs = render.find('TextInput');
// should have 4 inputs
expect(inputs.length).toBe(3);
expect(inputs.length).toBe(4);
// simulate user input
inputs.at(0).simulate('changeText', 'myFancyUsername');
......@@ -100,7 +100,7 @@ describe('RegisterForm component', () => {
inputs.at(3).simulate('changeText', 'ohNoItIsDifferent');
// simulate press register
await render.find('Button').at(1).simulate('press');
await render.find(Button).at(1).simulate('press');
// should call alert
expect(Alert.alert).toBeCalled();
......@@ -122,7 +122,7 @@ describe('RegisterForm component', () => {
let inputs = render.find('TextInput');
// should have 4 inputs
expect(inputs.length).toBe(3);
expect(inputs.length).toBe(4);
// simulate user input
inputs.at(0).simulate('changeText', 'myFancyUsername');
......@@ -138,7 +138,7 @@ describe('RegisterForm component', () => {
inputs.at(3).simulate('changeText', 'somepassword');
// simulate press register
await render.find('Button').at(1).simulate('press');
await render.find(Button).at(1).simulate('press');
// should call alert
expect(Alert.alert).toBeCalled();
......
......@@ -186,9 +186,8 @@ exports[`blog view screen component should renders correctly 1`] = `
style={
Object {
"color": "#444",
"fontFamily": "Roboto",
"fontFamily": "Roboto-Black",
"fontSize": 22,
"fontWeight": "800",
"paddingBottom": 8,
"paddingLeft": 12,
"paddingRight": 12,
......
......@@ -936,6 +936,7 @@ exports[`cature poster flags component should renders correctly for props false
"width": "100%",
}
}
testID="tagInput"
underlineColorAndroid="transparent"
value=""
/>
......@@ -2104,6 +2105,7 @@ exports[`cature poster flags component should renders correctly for props true 1
"width": "100%",
}
}
testID="tagInput"
underlineColorAndroid="transparent"
value=""
/>
......
import login from "./actions/login";
import sleep from '../src/common/helpers/sleep';
describe('Login Flow', () => {
beforeEach(async () => {
......@@ -12,13 +11,18 @@ describe('Login Flow', () => {
});
it('should show error', async () => {
// should login successfully
await expect(element(by.id('usernameInput'))).toBeVisible();
// login should be visible
await waitFor(element(by.id('usernameInput')))
.toBeVisible()
.withTimeout(10000);
// we moved the login logic to an action to avoid code duplication
await login('bad', 'credentials');
await sleep(1000);
// wait for the message
await waitFor(element(by.id('loginMsg')))
.toBeVisible()
.withTimeout(2000);
// it should show the error message
// according to the detox docs it should be toHaveText but it only works with toHaveLabel
......@@ -26,8 +30,10 @@ describe('Login Flow', () => {
});
it('should login successfully', async () => {
// should login successfully
await expect(element(by.id('usernameInput'))).toBeVisible();
// login should be visible
await waitFor(element(by.id('usernameInput')))
.toBeVisible()
.withTimeout(10000);
// we moved the login logic to an action to avoid code duplication
await login(process.env.loginUser, process.env.loginPass);
......
import deleteUser from "./helpers/deleteUser";
import deleteUser from './helpers/deleteUser';
import sleep from '../src/common/helpers/sleep';
describe('Register Flow', () => {
const username = 'e2euser' + ((Math.random() * 0xffffff) << 0).toString(16);
......@@ -9,15 +10,19 @@ describe('Register Flow', () => {
newInstance: true,
permissions: {
notifications: 'YES',
photos: 'YES',
},
});
});
afterAll(async () => {
deleteUser(username, password);
await deleteUser(username, password);
});
it('should register correctly', async () => {
console.log('registering', username);
// login shoulf be visible
await waitFor(element(by.id('usernameInput')))
.toBeVisible()
......@@ -43,9 +48,59 @@ describe('Register Flow', () => {
await element(by.id('registerPasswordConfirmInput')).typeText(password);
// press register
await element(by.id('registerCreateButton')).tap();
const registerButton = await element(by.id('registerCreateButton'));
await registerButton.tap();
await registerButton.tap();
await device.enableSynchronization();
// is the onboarding visible?
await waitFor(element(by.id('artTestID')))
.toBeVisible()
.withTimeout(5000);
// select art hashtag
await element(by.id('artTestID')).tap();
// move to next step
await element(by.id('wizardNext')).tap();
// wait for the suggested users list
await waitFor(element(by.id('suggestedUser0SubscriptionButton')))
.toBeVisible()
.withTimeout(10000);
// subscribe to the first user of the list
await element(by.id('suggestedUser0SubscriptionButton')).tap();
// move to next step
await element(by.id('wizardNext')).tap();
// wait for the channel setup
await waitFor(element(by.id('selectAvatar')))
.toBeVisible()
.withTimeout(10000);
// tap the select avatar button
await element(by.id('selectAvatar')).tap();
await sleep(3000);
// move to next step
await element(by.id('wizardNext')).tap();
// wait for the channel setup
await waitFor(element(by.id('RewardsOnboarding')))
.toBeVisible()
.withTimeout(10000);
// move to next step
await element(by.id('wizardNext')).tap();
// newsfeed should be visible
await waitFor(element(by.id('NewsfeedScreen')))
.toBeVisible()
.withTimeout(10000);
});
});
......@@ -9,7 +9,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<string></string>
<string/>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
......@@ -42,7 +42,7 @@
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string></string>
<string/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
......@@ -66,7 +66,7 @@
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to use your camera in order to upload images or videos</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<string/>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your microphone in order to record videos </string>
<key>NSPhotoLibraryAddUsageDescription</key>
......
......@@ -5,6 +5,7 @@
* @format
*/
const blacklist = require('metro-config/src/defaults/blacklist');
const defaultSourceExts = require('metro-config/src/defaults/defaults').sourceExts;
module.exports = {
resolver: {
......@@ -12,6 +13,9 @@ module.exports = {
blacklistRE: blacklist([
/ios\/Pods\/JitsiMeetSDK\/Frameworks\/JitsiMeet.framework\/assets\/node_modules\/react-native\/.*/,
]),
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts,
},
transformer: {
getTransformOptions: async () => ({
......
......@@ -18,7 +18,7 @@
"@hawkingnetwork/node-libs-react-native": "^1.0.10",
"@react-native-community/art": "^1.0.2",
"@react-native-community/async-storage": "^1.3.4",
"@react-native-community/cameraroll": "^1.2.1",
"@react-native-community/cameraroll": "^1.3.0",
"@react-native-community/netinfo": "^4.4.0",
"@sentry/react-native": "^1.0.9",
"crypto-js": "^3.1.9-1",
......@@ -64,6 +64,7 @@
"react-native-qrcode-svg": "^5.2.0",
"react-native-randombytes": "^3.5.3",
"react-native-reanimated": "^1.3.0",
"react-native-redash": "^8.6.0",
"react-native-screens": "^2.0.0-alpha.11",
"react-native-share": "^2.0.0",
"react-native-snap-carousel": "^3.8.2",
......@@ -107,7 +108,7 @@
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Minds.app",
"build": "xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"build": "RN_SRC_EXT=e2e.js xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
......@@ -115,7 +116,7 @@
},
"ios.sim.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/Minds.app",
"build": "xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"build": "RN_SRC_EXT=e2e.js xcodebuild -workspace ios/Minds.xcworkspace -scheme Minds -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
......
......@@ -27,6 +27,7 @@ import i18n from '../common/services/i18n.service';
import sessionService from '../common/services/session.service';
import delay from '../common/helpers/delay';
import apiService from '../common/services/api.service';
import { DISABLE_PASSWORD_INPUTS } from '../config/Config';
/**
* Register Form
......@@ -100,7 +101,7 @@ export default class RegisterForm extends Component {
<TextInput
style={[ComponentsStyle.loginInput, CommonStyle.marginTop2x]}
placeholder={i18n.t('auth.password')}
secureTextEntry={true}
secureTextEntry={!DISABLE_PASSWORD_INPUTS} // e2e workaround
autoCapitalize={'none'}
returnKeyType={'done'}
placeholderTextColor="#444"
......@@ -113,7 +114,7 @@ export default class RegisterForm extends Component {
<TextInput
style={[ComponentsStyle.loginInput, CommonStyle.marginTop2x]}
placeholder={i18n.t('auth.confirmpassword')}
secureTextEntry={true}
secureTextEntry={!DISABLE_PASSWORD_INPUTS} // e2e workaround
autoCapitalize={'none'}
returnKeyType={'done'}
placeholderTextColor="#444"
......
......@@ -239,7 +239,8 @@ const styles = StyleSheet.create({
textAlign: 'center',
},
link: {
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
warning: {
marginTop: 10,
......
......@@ -497,7 +497,8 @@ const styles = StyleSheet.create({
label: {
color: '#444',
fontSize: 16,
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
supportingTextContainer: {
flexDirection: 'row',
......
......@@ -135,6 +135,7 @@ export default class BlockchainWalletImportScreen extends Component {
const styles = StyleSheet.create({
title: {
fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
fontSize: 18,
color: '#444',
marginBottom: 8,
......
......@@ -159,7 +159,8 @@ const styles = StyleSheet.create({
label: {
paddingBottom: 3,
fontSize: 16,
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
letterSpacing: 1,
},
listAliasHighlight: {
......@@ -190,7 +191,7 @@ const styles = StyleSheet.create({
paddingRight: 3,
color: 'green',
fontSize: 20,
fontWeight: '800',
fontWeight: '700',
textAlign: 'right',
},
eth: {
......
......@@ -319,7 +319,8 @@ const styles = StyleSheet.create({
fontSize: 22,
color: '#444',
fontFamily: 'Roboto',
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
ownerBlockContainer: {
margin: 8,
......
......@@ -93,7 +93,7 @@ class Comment extends Component {
<CommentEditor setEditing={this.setEditing} comment={comment} store={this.props.store}/>
:
<DoubleTapText style={styles.message} selectable={true} onDoubleTap={this.showActions} selectable={false} onLongPress={this.showActions}>
<Text style={styles.username}>@{comment.ownerObj.username} </Text>
<Text style={styles.username} onPress={this._navToChannel} >@{comment.ownerObj.username} </Text>
{ comment.description &&
<Tags
navigation={this.props.navigation}
......@@ -300,7 +300,8 @@ const styles = StyleSheet.create({
fontSize: 14,
},
username: {
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black',
paddingRight: 8,
color: '#444',
},
......
import React from 'react';
import { View, StyleSheet, Animated, Easing, Dimensions } from 'react-native';
const { height, width } = Dimensions.get('window');
export default class Pulse extends React.Component {
constructor(props) {
super(props);
this.anim = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.anim, {
toValue: 1,
duration: this.props.interval,
easing: Easing.in,
})
.start();
}
render() {
const { size, pulseMaxSize, borderColor, backgroundColor, getStyle } = this.props;
return (
<View style={[styles.circleWrapper, {
width: pulseMaxSize,
height: pulseMaxSize,
marginLeft: -pulseMaxSize/2,
marginTop: -pulseMaxSize/2,
}]}>
<Animated.View
style={[styles.circle, {
borderColor,
backgroundColor,
width: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [size, pulseMaxSize]
}),
height: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [size, pulseMaxSize]
}),
borderRadius: pulseMaxSize/2,
opacity: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0]
})
}, getStyle && getStyle(this.anim)]}
/>
</View>
);
}
import { View, StyleSheet } from 'react-native';
import Animated, { Easing } from "react-native-reanimated";
import { bInterpolate, loop } from "react-native-redash";
const { Value, useCode, set } = Animated;
export default function(props) {
const animation = new Value(0);
useCode(
set(
animation,
loop({
toValue: 1,
duration: 1000,
easing: Easing.in(Easing.ease),
}),
),
[animation],
);
const scale = bInterpolate(animation, 1, 1.3);
const opacity = bInterpolate(animation, 1, 0);
const pulseMaxSize = Math.round(1.3 * props.size);
return (
<View
style={[
styles.circleWrapper,
{
width: pulseMaxSize,
height: pulseMaxSize,
marginLeft: -pulseMaxSize/2,
marginTop: -pulseMaxSize/2,
}
]}
>
<Animated.View
style={{
transform: [{scale}],
backgroundColor: 'red',
borderRadius: props.size / 2,
width: props.size,
height: props.size,
opacity,
}}
/>
</View>
);
}
const styles = StyleSheet.create({
circleWrapper: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
// left: width/8,
// top: height/2,
},
circle: {
borderWidth: 4 * StyleSheet.hairlineWidth,
},
circleWrapper: {
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
// left: width/8,
// top: height/2,
},
circle: {
borderWidth: 4 * StyleSheet.hairlineWidth,
},
});
\ No newline at end of file
import React from 'react';
import { View, Image, TouchableOpacity, Animated, Easing } from 'react-native';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import Pulse from './Pulse';
import FastImage from 'react-native-fast-image';
/**
* Based on https://github.com/wissile/react-native-pulse-anim
* Pulse avatar
*/
export default class PulseAnimAvatar extends React.Component {
constructor(props) {
super(props);
state = {};
this.state = {
circles: []
};
/**
* Derive state from props
* @param {object} nextProps
* @param {object} prevState
*/
static getDerivedStateFromProps(nextProps, prevState) {
if (
nextProps.size !== prevState.size ||
nextProps.avatar !== prevState.avatar
) {
return {
sizeStyle: {
width: nextProps.size,
height: nextProps.size,
},
imageStyle: {
width: nextProps.size,
height: nextProps.size,
borderRadius: nextProps.size / 2,
backgroundColor: nextProps.avatarBackgroundColor
},
avatarUri: {
uri: nextProps.avatar
},
};
}
return null;
}
this.counter = 1;
this.setInterval = null;
this.anim = new Animated.Value(1);
}
/**
* Render
*/
render() {
const {onPress} = this.props;
componentDidMount() {
this.setCircleInterval();
}
componentWillUnmount() {
clearInterval(this.setInterval);
}
setCircleInterval() {
this.setInterval = setInterval(this.addCircle.bind(this), this.props.interval);
this.addCircle();
}
addCircle() {
this.setState({ circles: [...this.state.circles, this.counter] });
this.counter++;
}
onPressIn() {
Animated.timing(this.anim, {
toValue: this.props.pressInValue,
duration: this.props.pressDuration,
easing: this.props.pressInEasing,
}).start(() => clearInterval(this.setInterval));
}
onPressOut() {
Animated.timing(this.anim, {
toValue: 1,
duration: this.props.pressDuration,
easing: this.props.pressOutEasing,
}).start(this.setCircleInterval.bind(this));
}
render() {
const { size, avatar, avatarBackgroundColor, interval, onPress } = this.props;
return (
<View style={{
// flex: 1,
backgroundColor: 'transparent',
// width: size,
// height: size,
justifyContent: 'center',
alignItems: 'center',
}}>
{this.state.circles.map((circle) => (
<Pulse
key={circle}
{...this.props}
/>
))}
<TouchableOpacity
activeOpacity={.5}
// onPressIn={this.onPressIn.bind(this)}
// onPressOut={this.onPressOut.bind(this)}
return (
<View style={styles.main}>
<Pulse
{...this.props}
/>
<TouchableOpacity
activeOpacity={.5}
onPress={onPress}
style={{
width: size,
height: size,
}}
>
<FastImage
source={{ uri: avatar }}
style={{
width: size,
height: size,
borderRadius: size/2,
backgroundColor: avatarBackgroundColor,
...this.props.style
}}
/>
</TouchableOpacity>
</View>
);
}
style={this.state.sizeStyle}
>
<FastImage
source={this.state.avatarUri}
style={[this.state.imageStyle, this.props.style]}
/>
</TouchableOpacity>
</View>
);
}
}
PulseAnimAvatar.defaultProps = {
interval: 2000,
size: 100,
pulseMaxSize: 200,
avatar: undefined,
avatarBackgroundColor: 'transparent',
pressInValue: 0.8,
pressDuration: 150,
pressInEasing: Easing.in,
pressOutEasing: Easing.in,
borderColor: '#1c1d1f',
backgroundColor: '#D1D1D1',
getStyle: undefined,
};
\ No newline at end of file
style: null,
};
const styles = StyleSheet.create({
main: {
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
},
});
......@@ -100,6 +100,7 @@ export default class TagInput extends Component {
keyboardType="default"
onSubmitEditing={this.addTag}
onEndEditing={this.addTag}
testID="tagInput"
/>
</ViewCmp>
......
......@@ -75,6 +75,7 @@ export default class TagSelect extends Component {
key={i}
onPress={() => this.toogle(tag)}
onLongPress={() => this.toogleOne(tag)}
testID={tag.value + 'TestID'}
>
<Text style={[styles.tagText, textStyle, tag.selected ? [CS.colorPrimary, textSelectedStyle] : null]}>#{tag.value}</Text>
</TouchableOpacity>)}
......
import React, { Component } from 'react';
import {View, StyleSheet} from 'react-native';
/**
* Mock video component for e2e (it takes too much cpu power in the simulator)
*/
export default class VideoBackground extends Component {
render() {
return <View style={[styles.container, {backgroundColor: 'black'}]}/>;
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
}
});
\ No newline at end of file
......@@ -59,13 +59,13 @@ export default class Wizard extends PureComponent<Props, State> {
<View style={[CS.rowJustifyCenter, CS.backgroundWhite, CS.padding2x, CS.marginTop4x, CS.marginBottom4x]}>
{first ?
<View style={{width:50}}/> :
<TouchableOpacity style={[{width:50}, CS.centered]} onPress={this.previous}>
<TouchableOpacity style={[{width:50}, CS.centered]} onPress={this.previous} testID="wizardPrevious">
<Icon size={34} name="keyboard-arrow-left" color={colors.primary}/>
</TouchableOpacity>}
<View style={[CS.flexContainer, CS.centered]}>
<Image source={require('./../../assets/logos/bulb.png')} style={{width:35, height:60}}/>
</View>
<TouchableOpacity style={[{width:50}, CS.centered]} onPress={this.next} disabled={!ready || this.state.waitingNext}>
<TouchableOpacity style={[{width:50}, CS.centered]} onPress={this.next} disabled={!ready || this.state.waitingNext} testID="wizardNext">
{
this.state.waitingNext ? <ActivityIndicator size="small"/> :
<Icon size={34} name="keyboard-arrow-right" color={ready ? colors.primary : colors.greyed}/>
......
import CameraRoll from '@react-native-community/cameraroll';
/**
* Image picker service e2e mock
*/
class ImagePickerService {
/**
* Show image picker selector
*
* We mock the image picker selector returning the first image of the cameraroll
* We can't controll the ios image selector because it runs in a separate process
*
* @param {string} title
* @param {string} type photo or video
*/
async show(title, type='photo') {
const params = {
first: 30,
assetType: 'All',
};
const result = await CameraRoll.getPhotos(params);
return {
uri: result.edges[0].node.image.uri,
type: result.edges[0].node.type,
fileName: result.edges[0].node.image.filename,
duration: result.edges[0].node.image.playableDuration,
width: result.edges[0].node.image.width,
height: result.edges[0].node.image.height,
}
}
}
export default new ImagePickerService();
\ No newline at end of file
......@@ -32,7 +32,7 @@ export default class AttachmentStore {
if (this.transcoding) {
return;
}
console.log('ATTACHING', media, extra);
if (this.uploading) {
// abort current upload
this.cancelCurrentUpload();
......@@ -52,26 +52,34 @@ export default class AttachmentStore {
this.setHasAttachment(true);
// correctly handle videos from ph:// paths on ios
if (
Platform.OS === 'ios' &&
media.type === 'video' &&
media.uri.startsWith('ph://')
) {
try {
this.transcoding = true;
const converted = await RNConvertPhAsset.convertVideoFromUrl({
url: media.uri,
convertTo: 'm4v',
quality: 'high',
});
media.type = converted.mimeType;
media.uri = converted.path;
media.filename = converted.filename;
} catch (error) {
Alert.alert('Error reading the video', 'Please try again');
} finally {
this.transcoding = false;
if (Platform.OS === 'ios') {
// correctly handle videos from ph:// paths on ios
if (media.type === 'video' && media.uri.startsWith('ph://')) {
try {
this.transcoding = true;
const converted = await RNConvertPhAsset.convertVideoFromUrl({
url: media.uri,
convertTo: 'm4v',
quality: 'high',
});
media.type = converted.mimeType;
media.uri = converted.path;
media.filename = converted.filename;
} catch (error) {
Alert.alert('Error reading the video', 'Please try again');
} finally {
this.transcoding = false;
}
}
// fix camera roll gif issue
if (media.type === 'image' && media.fileName) {
const extension = media.fileName.split('.').pop();
if (extension && extension.toLowerCase() === 'gif') {
media.type = 'image/gif';
const appleId = media.uri.substring(5, 41);
media.uri = `assets-library://asset/asset.GIF?id=${appleId}&ext=GIF`;
}
}
}
......
import {
Platform
} from 'react-native';
import DeviceInfo from 'react-native-device-info';
// export const MINDS_URI = 'https://www.minds.com/';
// export const MINDS_URI = 'http://dev.minds.io/';
// remember to update deeplink uri on AndroidManifest.xml !!!
// export const MINDS_URI = 'http://172.16.2.61:8080/';
export const MINDS_URI = 'https://www.minds.com/';
export const MINDS_API_URI = 'https://www.minds.com/';
export const NETWORK_TIMEOUT = 5000;
export const CONECTIVITY_CHECK_URI = 'https://www.minds.com/';
export const CONECTIVITY_CHECK_INTERVAL = 10000;
export const MINDS_URI_SETTINGS = {
//basicAuth: 'crypto:ohms',
};
export const MINDS_MAX_VIDEO_LENGTH = 5; // in minutes
export const SOCKET_URI = 'wss://ha-socket-io-us-east-1.minds.com:3030'
export const MINDS_CDN_URI = 'https://cdn.minds.com/';
export const MINDS_ASSETS_CDN_URI = 'https://cdn-assets.minds.com/';
// export const MINDS_CDN_URI = 'http://dev.minds.io/';
export const BLOCKCHAIN_URI = 'https://www.minds.com/api/v2/blockchain/proxy/';
// export const BLOCKCHAIN_URI = 'http://localhost:9545';
export const MINDS_LINK_URI = 'https://www.minds.com/';
export const CODE_PUSH_TOKEN = '';
/**
* Plataform dependant or fixed features
*/
export const MINDS_FEATURES = {
crypto: Platform.OS === 'ios' ? false : true,
};
/**
* Deeplink to screen/params maping
*/
export const MINDS_DEEPLINK = [
['groups/profile/:guid/feed', 'GroupView'],
['groups/profile/:guid', 'GroupView'],
['notifications', 'Notifications'],
['groups/:filter', 'GroupsList'],
['newsfeed/:guid', 'Activity'],
['media/:guid', 'Activity'],
['channels/:username', 'Channel'],
['blog/:filter', 'BlogList'],
['blog/view/:guid', 'BlogView'],
[':user/blog/:slug', 'BlogView'],
[':username', 'Channel'],
['wallet/tokens/:section', 'Wallet'],
];
export const DISABLE_PASSWORD_INPUTS = true;
// IF TRUE COMMENT THE SMS PERMISSIONS IN ANDROID MANIFEST TOO!!!
export const GOOGLE_PLAY_STORE = DeviceInfo.getBuildNumber() < 1050000000 && Platform.OS == 'android';
......@@ -59,5 +59,7 @@ export const MINDS_DEEPLINK = [
['wallet/tokens/:section', 'Wallet'],
];
export const DISABLE_PASSWORD_INPUTS = false;
// IF TRUE COMMENT THE SMS PERMISSIONS IN ANDROID MANIFEST TOO!!!
export const GOOGLE_PLAY_STORE = DeviceInfo.getBuildNumber() < 1050000000 && Platform.OS == 'android';
......@@ -26,6 +26,29 @@ import SubscriptionButton from '../channel/subscription/SubscriptionButton';
export default
@observer
class DiscoveryUser extends Component {
/**
* State
*/
state = {
guid: null,
};
/**
* Derive state from props
* @param {object} nextProps
* @param {object} prevState
*/
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.guid !== nextProps.row.item.guid) {
return {
guid: nextProps.row.item.guid,
source: { uri: MINDS_CDN_URI + 'icon/' + nextProps.row.item.guid + '/medium' }
}
}
return null;
}
/**
* Navigate To channel
......@@ -40,35 +63,38 @@ class DiscoveryUser extends Component {
}
}
/**
* Render right button
*/
renderRightButton() {
const channel = this.props.row.item;
if (channel.isOwner() || this.props.hideButtons || (channel.isOpen() && !channel.can(FLAG_SUBSCRIBE) )) {
return;
}
const testID = (this.props.testID) ? `${this.props.testID}SubscriptionButton` : 'subscriptionButton';
return (
<SubscriptionButton
channel={channel}
testID={testID}
/>
)
}
getChannel() {
return this.props.row.item;
}
/**
* Render
*/
render() {
const item = this.getChannel();
const avatarImg = { uri: MINDS_CDN_URI + 'icon/' + item.guid + '/medium' };
const {row, ...otherProps} = this.props;
return (
<TouchableOpacity style={styles.row} onPress={this._navToChannel}>
<Image source={avatarImg} style={styles.avatar} />
<TouchableOpacity style={styles.row} onPress={this._navToChannel} {...otherProps}>
<Image source={this.state.source} style={styles.avatar} />
<View style={[CommonStyle.flexContainerCenter]}>
<Text style={[styles.body, CommonStyle.fontXL]}>{item.name}</Text>
<Text style={[styles.body, CommonStyle.fontS, CommonStyle.colorMedium]}>@{item.username}</Text>
<Text style={[styles.body, CommonStyle.fontXL]}>{row.item.name}</Text>
<Text style={[styles.body, CommonStyle.fontS, CommonStyle.colorMedium]}>@{row.item.username}</Text>
</View>
{this.renderRightButton()}
</TouchableOpacity>
......
......@@ -301,7 +301,7 @@ class MindsVideo extends Component {
const entity = this.props.entity;
let {currentTime, duration, paused} = this.state;
const mustShow = (this.state.showOverlay && !isIOS) || this.state.paused;
const mustShow = (this.state.showOverlay && !isIOS) || this.state.paused && entity;
if (mustShow) {
const completedPercentage = this.getCurrentTimePercentage(currentTime, duration) * 100;
......
......@@ -2,6 +2,14 @@ import { NavigationActions, StackActions, SwitchActions } from 'react-navigation
let _navigator;
function getStateFrom(nav) {
let state = nav.routes[nav.index];
if (state.routes) {
state = getStateFrom(state);
}
return state;
}
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
......@@ -11,7 +19,7 @@ function getState() {
}
function getCurrentState() {
return _navigator.state.nav.routes[_navigator.state.nav.index];
return getStateFrom(_navigator.state.nav);
}
function navigate(routeName, params) {
......
......@@ -127,7 +127,12 @@ export default class ChannelSetupStep extends Component {
<View style={[CS.padding4x, CS.flexContainer, CS.rowJustifyStart, CS.alignCenter]}>
<Text style={[CS.fontXXL, CS.colorDark, CS.fontMedium]}>{i18n.t('onboarding.chooseAvatar')}</Text>
<View style={[CS.rowJustifyEnd, CS.flexContainer]}>
<TouchableCustom onPress={this.changeAvatarAction} style={[styles.avatar, CS.marginLeft3x, CS.border, CS.borderGreyed ]} disabled={this.saving}>
<TouchableCustom
onPress={this.changeAvatarAction}
style={[styles.avatar, CS.marginLeft3x, CS.border, CS.borderGreyed ]}
disabled={this.saving}
testID="selectAvatar"
>
{hasAvatar && <Image source={avatar} style={styles.wrappedAvatar} />}
<View style={[styles.tapOverlayView, hasAvatar ? null : CS.backgroundTransparent]}/>
......
......@@ -124,7 +124,7 @@ export default class RewardsStep extends Component {
return (
<View>
<View style={[style.cols, style.form]}>
<View style={[style.cols, style.form]} testID="RewardsOnboarding">
<PhoneInput
disabled={this.state.inProgress}
style={{ ...stylesheet.col, ...stylesheet.colFirst, ...stylesheet.phoneInput }}
......
......@@ -17,27 +17,37 @@ import i18n from '../../common/services/i18n.service';
@observer
export default class SuggestedChannelsStep extends Component {
/**
* Component did mount
*/
componentDidMount() {
this.props.onboarding.suggestedUsers.list.clearList();
this.props.onboarding.getSuggestedUsers();
}
renderUser = (user) => {
/**
* Render user
*/
renderUser = (user, index) => {
return <DiscoveryUser
row={{item: user}}
key={user.guid}
testID={`suggestedUser${index}`}
/>
}
/**
* Render
*/
render() {
return (
<View>
<View style={[CS.padding4x]}>
<View style={[CS.padding4x]} testID="suggestedChannelWizard">
<Text style={[CS.fontXXL, CS.colorDark, CS.fontMedium]}>{i18n.t('onboarding.suggestedChannels')}</Text>
<Text style={[CS.fontL, CS.colorDarkGreyed, CS.marginBottom3x]}>{i18n.t('onboarding.suggestedChannelsDescription')}</Text>
</View>
{!this.props.onboarding.suggestedUsers.list.loaded && <ActivityIndicator/>}
{this.props.onboarding.suggestedUsers.list.entities.map(user => this.renderUser(user))}
{this.props.onboarding.suggestedUsers.list.entities.map((user, i) => this.renderUser(user, i))}
</View>
);
}
......
......@@ -529,7 +529,8 @@ export const CommonStyle = StyleSheet.create({
fontWeight: '700'
},
extraBold: {
fontWeight: '800'
// fontWeight: '800'
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
fontThin: {
fontWeight: '200'
......@@ -567,7 +568,7 @@ export const CommonStyle = StyleSheet.create({
paddingBottom: 10,
},
modalTitle: {
fontWeight: '800',
fontFamily: 'Roboto-Black',
fontSize: 18,
color: '#444',
marginBottom: 8,
......
......@@ -96,7 +96,8 @@ const styles = StyleSheet.create({
titles: {
fontFamily: 'Roboto',
fontSize: 12,
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
color: '#444',
flex: 1,
}
......
......@@ -49,7 +49,7 @@ export default class WalletBalanceTokens extends Component {
}
render() {
let addresses = null;
if (this.props.wallet.addresses) {
......@@ -130,8 +130,8 @@ const styles = StyleSheet.create({
textAlign: 'right',
color: colors.primary,
fontSize: 16,
fontWeight: '800',
fontFamily: 'Roboto',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
addressesEthBalance: {
fontSize: 12,
......
......@@ -231,7 +231,8 @@ const styles = StyleSheet.create({
},
count: {
fontSize: 24,
fontWeight: '800',
// fontWeight: '800',
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
flexGrow: 1,
},
positive: {
......
......@@ -27,7 +27,7 @@ export default class ContributionRow extends PureComponent {
const item = this.props.item;
const selected = this.state.selected;
const color = selected ? [CommonStyle.colorBlack, {fontWeight: '800'}] : [CommonStyle.colorDark];
const color = selected ? [CommonStyle.colorBlack, {fontFamily: 'Roboto-Black'}] : [CommonStyle.colorDark];
const detail = selected ? this.getDetail() : null;
......
......@@ -100,9 +100,9 @@ export default class ContributionsView extends Component {
return (
<View style={styles.header}>
<View style={[CommonStyle.rowJustifyStart, styles.row]}>
<Text style={[CommonStyle.flexContainer, {fontWeight: '800', fontSize: 10 }]}>{i18n.t('wallet.contributions.date')}</Text>
<Text style={[CommonStyle.flexContainer, {fontWeight: '800', fontSize: 10 }]}>{i18n.t('wallet.contributions.score')}</Text>
<Text style={[CommonStyle.flexContainer, {fontWeight: '800', fontSize: 10 }]}>{i18n.t('wallet.contributions.share')}</Text>
<Text style={[CommonStyle.flexContainer, styles.text]}>{i18n.t('wallet.contributions.date')}</Text>
<Text style={[CommonStyle.flexContainer, styles.text]}>{i18n.t('wallet.contributions.score')}</Text>
<Text style={[CommonStyle.flexContainer, styles.text]}>{i18n.t('wallet.contributions.share')}</Text>
</View>
</View>
);
......@@ -158,6 +158,10 @@ const styles = StyleSheet.create({
//borderBottomWidth: 1,
//borderBottomColor: '#ececec',
},
text: {
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
fontSize: 10,
},
row: {
paddingTop: 16,
paddingBottom: 16,
......
......@@ -925,7 +925,7 @@
resolved "https://registry.yarnpkg.com/@react-native-community/async-storage/-/async-storage-1.6.2.tgz#a19ca7149c4dfe8216f2330e6b1ebfe2d075ef92"
integrity sha512-EJGsbrHubK1mGxPjWB74AaHAd5m9I+Gg2RRPZzMK6org7QOU9WOBnIMFqoeVto3hKOaEPlk8NV74H6G34/2pZQ==
"@react-native-community/cameraroll@^1.2.1":
"@react-native-community/cameraroll@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@react-native-community/cameraroll/-/cameraroll-1.3.0.tgz#a340334440f4d08280da839130ef51c931b07483"
integrity sha512-QJl9N34euvGU7s/Gn6jhsqi70O4SmxFxuy+yBnW7ehE8qtPYO91gyLLrtiWdTfYvuVCUNvX/G0LKJQLm8SojAA==
......@@ -1522,6 +1522,11 @@ abort-controller@^3.0.0:
dependencies:
event-target-shim "^5.0.0"
abs-svg-path@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
integrity sha1-32Acjo0roQ1KdtYl4japo5wnI78=
absolute-path@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7"
......@@ -7408,6 +7413,13 @@ normalize-path@^2.1.1:
dependencies:
remove-trailing-separator "^1.0.1"
normalize-svg-path@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.0.1.tgz#6f729ad6b70bb4ca4eff2fe4b107489efe1d56fe"
integrity sha1-b3Ka1rcLtMpO/y/ksQdInv4dVv4=
dependencies:
svg-arc-to-cubic-bezier "^3.0.0"
normalize-url@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
......@@ -7939,6 +7951,11 @@ parse-path@^4.0.0:
is-ssh "^1.3.0"
protocols "^1.4.0"
parse-svg-path@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
integrity sha1-en7A0esG+lMlx9PgCbhZoJtdSes=
parse-url@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f"
......@@ -8772,6 +8789,16 @@ react-native-reanimated@^1.3.0:
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.4.0.tgz#7f1acbf9be08492d834f512700570978052be2f9"
integrity sha512-tO7nSNNP+iRLVbkcSS5GXyDBb7tSI02+XuRL3/S39EAr35rnvUy2JfeLUQG+fWSObJjnMVhasUDEUwlENk8IXw==
react-native-redash@^8.6.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-8.6.0.tgz#a99f1e714f076816b17f44cbe77661a915079feb"
integrity sha512-Hjaz+rcyK/8w84/cPgJVajcE/86y8njw/OVp74qXRnnzhEo+n4O5UGcmP+WyVxCZ2mtgfYJevoqGP5GczEzM8Q==
dependencies:
abs-svg-path "^0.1.1"
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
use-memo-one "^1.1.1"
react-native-safe-area-view@^0.14.1, react-native-safe-area-view@^0.14.6:
version "0.14.8"
resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.14.8.tgz#ef33c46ff8164ae77acad48c3039ec9c34873e5b"
......@@ -10354,6 +10381,11 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
svg-arc-to-cubic-bezier@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
swarm-js@0.1.39:
version "0.1.39"
resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.39.tgz#79becb07f291d4b2a178c50fee7aa6e10342c0e8"
......@@ -10909,6 +10941,11 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-memo-one@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c"
integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
......