feat: new invite layout

This commit is contained in:
Bettenbuk Zoltan 2019-08-28 12:25:51 +02:00 committed by Zoltan Bettenbuk
parent c1598b7376
commit a93bd422d3
9 changed files with 140 additions and 67 deletions

View File

@ -25,6 +25,12 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-cancel:before {
content: "\e91c";
}
.icon-check:before {
content: "\e91b";
}
.icon-send:before {
content: "\e911";
}

Binary file not shown.

View File

@ -54,6 +54,8 @@
<glyph unicode="&#xe918;" glyph-name="camera" d="M726 576l170 170v-468l-170 170v-150c0-24-20-42-44-42h-512c-24 0-42 18-42 42v428c0 24 18 42 42 42h512c24 0 44-18 44-42v-150z" />
<glyph unicode="&#xe919;" glyph-name="camera-disabled" d="M140 938l756-756-54-54-136 136c-6-4-16-8-24-8h-512c-24 0-42 18-42 42v428c0 24 18 42 42 42h32l-116 116zM896 746v-456l-478 478h264c24 0 44-18 44-42v-150z" />
<glyph unicode="&#xe91a;" glyph-name="volume" d="M598 886c172-38 298-192 298-374s-126-336-298-374v88c124 36 212 150 212 286s-88 250-212 286v88zM704 512c0-76-42-140-106-172v344c64-32 106-96 106-172zM128 640h170l214 214v-684l-214 214h-170v256z" />
<glyph unicode="&#xe91b;" glyph-name="check" d="M384 334l452 452 60-60-512-512-238 238 60 60z" />
<glyph unicode="&#xe91c;" glyph-name="cancel" d="M726 358l-154 154 154 154-60 60-154-154-154 154-60-60 154-154-154-154 60-60 154 154 154-154zM512 938q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125z" />
<glyph unicode="&#xe91d;" glyph-name="feedback" d="M42.667 128h170.667v512h-170.667v-512zM981.333 597.333c0 46.933-38.4 85.333-85.333 85.333h-269.227l40.533 194.987 1.28 13.653c0 17.493-7.253 33.707-18.773 45.227l-45.227 44.8-280.747-281.173c-15.787-15.36-25.173-36.693-25.173-60.16v-426.667c0-46.933 38.4-85.333 85.333-85.333h384c35.413 0 65.707 21.333 78.507 52.053l128.853 300.8c3.84 9.813 5.973 20.053 5.973 31.147v81.493l-0.427 0.427 0.427 3.413z" />
<glyph unicode="&#xe91e;" glyph-name="raised-hand" d="M982 790v-620c0-94-78-170-172-170h-310c-46 0-90 18-122 50l-336 342s54 52 56 52c10 8 22 12 34 12 10 0 18-2 26-6 2 0 184-104 184-104v508c0 36 28 64 64 64s64-28 64-64v-300h42v406c0 36 28 64 64 64s64-28 64-64v-406h42v364c0 36 28 64 64 64s64-28 64-64v-364h44v236c0 36 28 64 64 64s64-28 64-64z" />
<glyph unicode="&#xe91f;" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,11 @@ import styles, { AVATAR_SIZE, UNDERLAY_COLOR } from './styles';
type Props = {
/**
* If true, only the avatar gets rendered, no lines of text.
*/
avatarOnly?: boolean,
/**
* Preferred size of the avatar.
*/
@ -76,6 +81,7 @@ export default class AvatarListItem extends Component<Props> {
*/
render() {
const {
avatarOnly,
avatarSize = AVATAR_SIZE,
avatarStyle
} = this.props;
@ -92,7 +98,7 @@ export default class AvatarListItem extends Component<Props> {
size = { avatarSize }
style = { avatarStyle }
url = { avatar } />
<Container style = { styles.listItemDetails }>
{ avatarOnly || <Container style = { styles.listItemDetails }>
<Text
numberOfLines = { 1 }
style = { [
@ -103,7 +109,7 @@ export default class AvatarListItem extends Component<Props> {
{ title }
</Text>
{this._renderItemLines(lines)}
</Container>
</Container>}
{ this.props.children }
</Container>
);

View File

@ -106,6 +106,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
this.state = this.defaultState;
this._keyExtractor = this._keyExtractor.bind(this);
this._renderInvitedItem = this._renderInvitedItem.bind(this);
this._renderItem = this._renderItem.bind(this);
this._renderSeparator = this._renderSeparator.bind(this);
this._onClearField = this._onClearField.bind(this);
@ -138,7 +139,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
_addPeopleEnabled,
_dialOutEnabled
} = this.props;
const { inviteItems } = this.state;
const { inviteItems, selectableItems } = this.state;
let placeholderKey = 'searchPlaceholder';
@ -187,14 +188,23 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
value = { this.state.fieldValue } />
{ this._renderAndroidClearButton() }
</View>
<FlatList
ItemSeparatorComponent = { this._renderSeparator }
data = { this.state.selectableItems }
extraData = { inviteItems }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderItem }
style = { styles.resultList } />
{ Boolean(inviteItems.length) && <View style = { styles.invitedList }>
<FlatList
data = { inviteItems }
horizontal = { true }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderInvitedItem } />
</View> }
<View style = { styles.resultList }>
<FlatList
ItemSeparatorComponent = { this._renderSeparator }
data = { selectableItems }
extraData = { inviteItems }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderItem } />
</View>
</SafeAreaView>
</KeyboardAvoidingView>
</SlidingView>
@ -210,6 +220,33 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
this.setState(this.defaultState);
}
/**
* Returns an object capable of being rendered by an {@code AvatarListItem}.
*
* @param {Object} flatListItem - An item of the data array of the {@code FlatList}.
* @returns {?Object}
*/
_getRenderableItem(flatListItem) {
const { item } = flatListItem;
switch (item.type) {
case 'phone':
return {
avatar: 'icon://phone',
key: item.number,
title: item.number
};
case 'user':
return {
avatar: item.avatar,
key: item.id || item.user_id,
title: item.name
};
default:
return null;
}
}
_invite: Array<Object> => Promise<Array<Object>>
_isAddDisabled: () => boolean;
@ -303,7 +340,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
});
} else {
// Item is not selected yet, need to add to the list.
const items: Array<*> = inviteItems.concat(item);
const items: Array<Object> = inviteItems.concat(item);
this.setState({
inviteItems: _.sortBy(items, [ 'name', 'number' ])
@ -344,27 +381,8 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
*/
_performSearch(query) {
this._query(query).then(results => {
const { inviteItems } = this.state;
let selectableItems = results.filter(result => {
switch (result.type) {
case 'phone':
return result.allowed && result.number
&& !inviteItems.find(
_.matchesProperty('number', result.number));
case 'user':
return !inviteItems.find(
(result.user_id && _.matchesProperty('id', result.id))
|| (result.user_id && _.matchesProperty('user_id', result.user_id)));
default:
return false;
}
});
selectableItems = _.sortBy(selectableItems, [ 'name', 'number' ]);
this.setState({
selectableItems: this.state.inviteItems.concat(selectableItems)
selectableItems: _.sortBy(results, [ 'name', 'number' ])
});
})
.finally(() => {
@ -378,8 +396,6 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
_query: (string) => Promise<Array<Object>>;
_renderItem: Object => ?React$Element<*>
/**
* Renders a button to clear the text field on Android.
*
@ -405,8 +421,46 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
);
}
_renderInvitedItem: Object => ?React$Element<*>
/**
* Renders a single item in the {@code FlatList}.
* Renders a single item in the invited {@code FlatList}.
*
* @param {Object} flatListItem - An item of the data array of the
* {@code FlatList}.
* @param {number} index - The index of the currently rendered item.
* @returns {?React$Element<*>}
*/
_renderInvitedItem(flatListItem, index) {
const { item } = flatListItem;
const renderableItem = this._getRenderableItem(flatListItem);
return (
<TouchableOpacity onPress = { this._onPressItem(item) } >
<View
pointerEvents = 'box-only'
style = { styles.itemWrapper }>
<AvatarListItem
avatarOnly = { true }
avatarSize = { AVATAR_SIZE }
avatarStyle = { styles.avatar }
avatarTextStyle = { styles.avatarText }
item = { renderableItem }
key = { index }
linesStyle = { styles.itemLinesStyle }
titleStyle = { styles.itemText } />
<Icon
name = 'cancel'
style = { styles.unselectIcon } />
</View>
</TouchableOpacity>
);
}
_renderItem: Object => ?React$Element<*>
/**
* Renders a single item in the search result {@code FlatList}.
*
* @param {Object} flatListItem - An item of the data array of the
* {@code FlatList}.
@ -417,28 +471,20 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
const { item } = flatListItem;
const { inviteItems } = this.state;
let selected = false;
let renderableItem;
const renderableItem = this._getRenderableItem(flatListItem);
if (!renderableItem) {
return null;
}
switch (item.type) {
case 'phone':
selected
= inviteItems.find(_.matchesProperty('number', item.number));
renderableItem = {
avatar: 'icon://phone',
key: item.number,
title: item.number
};
selected = inviteItems.find(_.matchesProperty('number', item.number));
break;
case 'user':
selected
= item.id
? inviteItems.find(_.matchesProperty('id', item.id))
: inviteItems.find(_.matchesProperty('user_id', item.user_id));
renderableItem = {
avatar: item.avatar,
key: item.id || item.user_id,
title: item.name
};
selected = item.id
? inviteItems.find(_.matchesProperty('id', item.id))
: inviteItems.find(_.matchesProperty('user_id', item.user_id));
break;
default:
return null;
@ -449,11 +495,6 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
<View
pointerEvents = 'box-only'
style = { styles.itemWrapper }>
<Icon
name = { selected
? 'radio_button_checked'
: 'radio_button_unchecked' }
style = { styles.radioButton } />
<AvatarListItem
avatarSize = { AVATAR_SIZE }
avatarStyle = { styles.avatar }
@ -462,6 +503,9 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<Props, State> {
key = { index }
linesStyle = { styles.itemLinesStyle }
titleStyle = { styles.itemText } />
{ selected && <Icon
name = 'check'
style = { styles.selectedIcon } /> }
</View>
</TouchableOpacity>
);

View File

@ -1,6 +1,6 @@
// @flow
import { ColorPalette } from '../../../../base/styles';
import { BoxModel, ColorPalette } from '../../../../base/styles';
export const AVATAR_SIZE = 40;
export const DARK_GREY = 'rgb(28, 32, 37)';
@ -15,7 +15,7 @@ export default {
},
avatarText: {
color: 'rgb(28, 32, 37)',
color: DARK_GREY,
fontSize: 12
},
@ -48,7 +48,12 @@ export default {
alignItems: 'stretch',
backgroundColor: ColorPalette.white,
flex: 1,
flexDirection: 'column'
flexDirection: 'column',
justifyContent: 'flex-start'
},
invitedList: {
padding: 3
},
itemLinesStyle: {
@ -68,13 +73,8 @@ export default {
paddingLeft: 5
},
radioButton: {
color: DARK_GREY,
fontSize: 16,
padding: 2
},
resultList: {
flex: 1,
padding: 5
},
@ -88,6 +88,13 @@ export default {
paddingVertical: 7
},
selectedIcon: {
color: DARK_GREY,
fontSize: 20,
marginRight: BoxModel.margin,
padding: 2
},
separator: {
borderBottomColor: LIGHT_GREY,
borderBottomWidth: 1,
@ -115,5 +122,13 @@ export default {
flexDirection: 'row',
justifyContent: 'center',
width: ICON_SIZE + 16
},
unselectIcon: {
color: LIGHT_GREY,
fontSize: 16,
left: AVATAR_SIZE / -3,
position: 'relative',
top: AVATAR_SIZE / -3
}
};