feat: new invite layout
This commit is contained in:
parent
c1598b7376
commit
a93bd422d3
|
@ -25,6 +25,12 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-cancel:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
.icon-check:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
.icon-send:before {
|
||||
content: "\e911";
|
||||
}
|
||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -54,6 +54,8 @@
|
|||
<glyph unicode="" 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="" 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="" 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="" glyph-name="check" d="M384 334l452 452 60-60-512-512-238 238 60 60z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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 |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
File diff suppressed because one or more lines are too long
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue