Add hint box with dynamic join button

This commit is contained in:
zbettenbuk 2018-02-16 10:06:03 -06:00 committed by Lyubo Marinov
parent 547ddee3a5
commit e23d4317eb
8 changed files with 178 additions and 17 deletions

View File

@ -49,6 +49,7 @@
"appDescription": "Go ahead, video chat with the whole team. In fact, invite everyone you know. __app__ is a fully encrypted, 100% open source video conferencing solution that you can use all day, every day, for free — with no account needed.",
"audioOnlyLabel": "Voice",
"go": "GO",
"hintText": "Enter a room name you want to join to, or simply create a new room name, eg. MeetingWithJohn",
"join": "JOIN",
"privacy": "Privacy",
"roomname": "Enter room name",

View File

@ -5,12 +5,21 @@ import { ActivityIndicator } from 'react-native';
import { ColorPalette } from '../../../styles';
type Props = {
/**
* Prop to set the size of the indicator. This is the same as the
* prop of the native component.
*/
size: 'large' | 'small'
};
/**
* An animated, large react-native {@link ActivityIndicator} which is considered
* a suitable visualization of long-running processes with indeterminate amounts
* of work to be done.
*/
export default class LoadingIndicator extends Component<*> {
export default class LoadingIndicator extends Component<Props> {
/**
* Implements React's {@link Component#render()}.
*
@ -22,7 +31,7 @@ export default class LoadingIndicator extends Component<*> {
<ActivityIndicator
animating = { true }
color = { ColorPalette.white }
size = { 'large' }
size = { this.props.size || 'large' }
{ ...this.props } />
);
}

View File

@ -9,6 +9,11 @@ import { appNavigate } from '../../app';
*/
type Props = {
/**
* Indicates if the list is disabled or not.
*/
disabled: boolean,
/**
* The redux store's {@code dispatch} function.
*/
@ -31,7 +36,9 @@ export default class AbstractRecentList extends Component<Props> {
* @returns {void}
*/
_onJoin(room) {
room && this.props.dispatch(appNavigate(room));
const { disabled, dispatch } = this.props;
!disabled && room && dispatch(appNavigate(room));
}
/**

View File

@ -28,8 +28,8 @@ class RecentList extends AbstractRecentList {
*
* @inheritdoc
*/
constructor() {
super();
constructor(props) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._getAvatarStyle = this._getAvatarStyle.bind(this);
@ -47,16 +47,22 @@ class RecentList extends AbstractRecentList {
* @returns {ReactElement}
*/
render() {
if (!this.props || !this.props._recentList) {
const { _recentList, disabled } = this.props;
if (!_recentList) {
return null;
}
const listViewDataSource
= this.dataSource.cloneWithRows(
getRecentRooms(this.props._recentList));
getRecentRooms(_recentList));
return (
<View style = { styles.container }>
<View
style = { [
styles.container,
disabled ? styles.containerDisabled : null
] }>
<ListView
dataSource = { listViewDataSource }
enableEmptySections = { true }

View File

@ -87,6 +87,13 @@ export default createStyleSheet({
flex: 1
},
/**
* Shows the container disabled.
*/
containerDisabled: {
opacity: 0.2
},
/**
* Second line of the list (date). May be extended with server name later.
*/

View File

@ -14,6 +14,11 @@ import { generateRoomWithoutSeparator } from '../functions';
*/
type Props = {
/**
* Boolean to indicate if the room field is focused or not.
*/
_fieldFocused: boolean,
/**
* The user's profile.
*/

View File

@ -1,5 +1,6 @@
import React from 'react';
import {
Animated,
Keyboard,
SafeAreaView,
Switch,
@ -59,8 +60,13 @@ class WelcomePage extends AbstractWelcomePage {
constructor(props) {
super(props);
this.state.hintBoxAnimation = new Animated.Value(0);
this._getHintBoxStyle = this._getHintBoxStyle.bind(this);
this._onFieldFocusChange = this._onFieldFocusChange.bind(this);
this._onShowSideBar = this._onShowSideBar.bind(this);
this._onStartAudioOnlyChange = this._onStartAudioOnlyChange.bind(this);
this._renderHintBox = this._renderHintBox.bind(this);
}
/**
@ -124,7 +130,9 @@ class WelcomePage extends AbstractWelcomePage {
autoComplete = { false }
autoCorrect = { false }
autoFocus = { false }
onBlur = { this._onFieldFocusChange(false) }
onChangeText = { this._onRoomChange }
onFocus = { this._onFieldFocusChange(true) }
onSubmitEditing = { this._onJoin }
placeholder = { t('welcomepage.roomname') }
placeholderTextColor = { PLACEHOLDER_TEXT_COLOR }
@ -133,9 +141,9 @@ class WelcomePage extends AbstractWelcomePage {
underlineColorAndroid = 'transparent'
value = { this.state.room } />
{
this._renderJoinButton()
this._renderHintBox()
}
<RecentList />
<RecentList disabled = { this.state._fieldFocused } />
</SafeAreaView>
<AppSettings />
</View>
@ -144,6 +152,50 @@ class WelcomePage extends AbstractWelcomePage {
);
}
/**
* Constructs a style array to handle the hint box animation.
*
* @private
* @returns {Array<Object>}
*/
_getHintBoxStyle() {
return [
styles.hintContainer,
{
opacity: this.state.hintBoxAnimation
}
];
}
/**
* Callback for when the room field's focus changes so the hint box
* must be rendered or removed.
*
* @private
* @param {boolean} focused - The focused state of the field.
* @returns {Function}
*/
_onFieldFocusChange(focused) {
return () => {
if (focused) {
this.setState({
_fieldFocused: true
});
}
Animated.timing(this.state.hintBoxAnimation, {
duration: 300,
toValue: focused ? 1 : 0
}).start(animationState => {
if (animationState.finished && !focused) {
this.setState({
_fieldFocused: false
});
}
});
};
}
/**
* Toggles the side bar.
*
@ -171,6 +223,36 @@ class WelcomePage extends AbstractWelcomePage {
}));
}
/**
* Renders the hint box if necessary.
*
* @private
* @returns {React$Node}
*/
_renderHintBox() {
if (this.state._fieldFocused) {
const { t } = this.props;
return (
<Animated.View
style = { this._getHintBoxStyle() }>
<View style = { styles.hintTextContainer } >
<Text>
{ t('welcomepage.hintText') }
</Text>
</View>
<View style = { styles.hintButtonContainer } >
{
this._renderJoinButton()
}
</View>
</Animated.View>
);
}
return null;
}
/**
* Renders the join button.
*
@ -188,7 +270,9 @@ class WelcomePage extends AbstractWelcomePage {
// modify non-native children.
children = (
<View>
<LoadingIndicator color = { styles.buttonText.color } />
<LoadingIndicator
color = { styles.buttonText.color }
size = 'small' />
</View>
);
} else {
@ -201,12 +285,17 @@ class WelcomePage extends AbstractWelcomePage {
/* eslint-enable no-extra-parens */
const buttonDisabled = this._isJoinDisabled();
return (
<TouchableHighlight
accessibilityLabel = { 'Tap to Join.' }
disabled = { this._isJoinDisabled() }
disabled = { buttonDisabled }
onPress = { this._onJoin }
style = { styles.button }
style = { [
styles.button,
buttonDisabled ? styles.buttonDisabled : null
] }
underlayColor = { ColorPalette.white }>
{
children

View File

@ -54,10 +54,17 @@ export default createStyleSheet({
borderColor: ColorPalette.blue,
borderRadius: 4,
borderWidth: 1,
height: 40,
height: 30,
justifyContent: 'center',
marginBottom: BoxModel.margin,
marginTop: BoxModel.margin
paddingHorizontal: 20
},
/**
* Renders the button visually disabled.
*/
buttonDisabled: {
backgroundColor: '#cccccc',
borderColor: '#999999'
},
/**
@ -66,7 +73,7 @@ export default createStyleSheet({
buttonText: {
alignSelf: 'center',
color: ColorPalette.white,
fontSize: 18
fontSize: 14
},
/**
@ -86,6 +93,36 @@ export default createStyleSheet({
justifyContent: 'space-between'
},
/**
* Container for the button on the hint box.
*/
hintButtonContainer: {
flexDirection: 'row',
justifyContent: 'flex-end'
},
/**
* Container for the text on the hint box.
*/
hintTextContainer: {
marginBottom: 2 * BoxModel.margin
},
/**
* Container for the hint box.
*/
hintContainer: {
backgroundColor: ColorPalette.white,
borderColor: ColorPalette.white,
borderRadius: 4,
borderWidth: 1,
flexDirection: 'column',
marginVertical: 5,
overflow: 'hidden',
paddingHorizontal: BoxModel.padding,
paddingVertical: 2 * BoxModel.padding
},
/**
* Container for the items in the side bar.
*/