[RN] Add recent-list feature
This commit is contained in:
parent
45c405de0e
commit
1e0550c746
|
@ -25,12 +25,12 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-public:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
.icon-event_note:before {
|
||||
content: "\e616";
|
||||
}
|
||||
.icon-public:before {
|
||||
content: "\e80b";
|
||||
}
|
||||
.icon-timer:before {
|
||||
content: "\e425";
|
||||
}
|
||||
|
|
|
@ -90,22 +90,20 @@ export default class Storage {
|
|||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this storage in an
|
||||
* async manner. This method is required for those cases where we need
|
||||
* the stored data but we're not sure yet whether the {@code Storage}
|
||||
* is already initialised or not - e.g. on app start.
|
||||
* async manner. This method is required for those cases where we need the
|
||||
* stored data but we're not sure yet whether the {@code Storage} is already
|
||||
* initialised or not - e.g. on app start.
|
||||
*
|
||||
* @private
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getItemAsync(key) {
|
||||
return new Promise(resolve => {
|
||||
return new Promise(
|
||||
resolve =>
|
||||
AsyncStorage.getItem(
|
||||
`${String(this._keyPrefix)}${key}`,
|
||||
(error, result) => {
|
||||
resolve(result ? result : null);
|
||||
});
|
||||
});
|
||||
(error, result) => resolve(result ? result : null)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,22 +3,9 @@
|
|||
import { Component } from 'react';
|
||||
import { ListView } from 'react-native';
|
||||
|
||||
import { getRecentRooms } from '../functions';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link AbstractRecentList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The {@code ListView.DataSource} to be used for the {@code ListView}.
|
||||
* Its content comes from the native implementation of
|
||||
* {@code window.localStorage}.
|
||||
*/
|
||||
dataSource: Object
|
||||
}
|
||||
import { getRecentRooms } from '../functions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link AbstractRecentList}
|
||||
|
@ -26,28 +13,41 @@ type State = {
|
|||
type Props = {
|
||||
|
||||
/**
|
||||
* Redux store dispatch function.
|
||||
* The redux store's {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
}
|
||||
dispatch: Dispatch<*>
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which represents the list of
|
||||
* conferences recently joined, similar to how a list of last dialed
|
||||
* numbers list would do on a mobile
|
||||
* The type of the React {@code Component} state of {@link AbstractRecentList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The {@code ListView.DataSource} to be used for the {@code ListView}. Its
|
||||
* content comes from the native implementation of
|
||||
* {@code window.localStorage}.
|
||||
*/
|
||||
dataSource: Object
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which represents the list of conferences
|
||||
* recently joined, similar to how a list of last dialed numbers list would do
|
||||
* on a mobile device.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class AbstractRecentList extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* The datasource that backs the {@code ListView}
|
||||
* The datasource that backs the {@code ListView}.
|
||||
*/
|
||||
listDataSource = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) =>
|
||||
r1.conference !== r2.conference
|
||||
&& r1.dateTimeStamp !== r2.dateTimeStamp
|
||||
});;
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractRecentList} instance.
|
||||
|
@ -67,13 +67,23 @@ export default class AbstractRecentList extends Component<Props, State> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentWillMount() {
|
||||
// this must be done asynchronously because we don't have the storage
|
||||
// initiated on app startup immediately.
|
||||
getRecentRooms().then(rooms => {
|
||||
// The following must be done asynchronously because we don't have the
|
||||
// storage initiated on app startup immediately.
|
||||
getRecentRooms()
|
||||
.then(rooms =>
|
||||
this.setState({
|
||||
dataSource: this.listDataSource.cloneWithRows(rooms)
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the selected room.
|
||||
*
|
||||
* @param {string} room - The selected room.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoin(room) {
|
||||
room && this.props.dispatch(appNavigate(room));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,17 +95,4 @@ export default class AbstractRecentList extends Component<Props, State> {
|
|||
_onSelect(room) {
|
||||
return this._onJoin.bind(this, room);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the selected room.
|
||||
*
|
||||
* @param {string} room - The selected room.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onJoin(room) {
|
||||
if (room) {
|
||||
this.props.dispatch(appNavigate(room));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ import React from 'react';
|
|||
import { ListView, Text, TouchableHighlight, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from '../../base/font-icons';
|
||||
|
||||
import AbstractRecentList from './AbstractRecentList';
|
||||
import styles, { UNDERLAY_COLOR } from './styles';
|
||||
|
||||
import { Icon } from '../../base/font-icons';
|
||||
|
||||
/**
|
||||
* The native container rendering the list of the recently joined rooms.
|
||||
*
|
||||
|
@ -15,11 +15,11 @@ import { Icon } from '../../base/font-icons';
|
|||
class RecentList extends AbstractRecentList {
|
||||
/**
|
||||
* Initializes a new {@code RecentList} instance.
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._getAvatarStyle = this._getAvatarStyle.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._renderConfDuration = this._renderConfDuration.bind(this);
|
||||
|
@ -28,8 +28,8 @@ class RecentList extends AbstractRecentList {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}. Renders a list of
|
||||
* recently joined rooms.
|
||||
* Implements React's {@link Component#render()}. Renders a list of recently
|
||||
* joined rooms.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
|
@ -49,11 +49,97 @@ class RecentList extends AbstractRecentList {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the style array of the avatar based on if the conference was a
|
||||
* home or remote server conference (based on current app setting).
|
||||
*
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_getAvatarStyle(recentListEntry) {
|
||||
const avatarStyles = [ styles.avatar ];
|
||||
|
||||
if (recentListEntry.baseURL !== this.props._homeServer) {
|
||||
avatarStyles.push(
|
||||
this._getColorForServerName(recentListEntry.serverName));
|
||||
}
|
||||
|
||||
return avatarStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a style (color) based on the server name, so then the same server
|
||||
* will always be rendered with the same avatar color.
|
||||
*
|
||||
* @param {string} serverName - The recent list entry being rendered.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getColorForServerName(serverName) {
|
||||
let nameHash = 0;
|
||||
|
||||
for (let i = 0; i < serverName.length; i++) {
|
||||
nameHash += serverName.codePointAt(i);
|
||||
}
|
||||
|
||||
return styles[`avatarRemoteServer${(nameHash % 5) + 1}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the conference duration if available.
|
||||
*
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderConfDuration({ conferenceDurationString }) {
|
||||
if (conferenceDurationString) {
|
||||
return (
|
||||
<View style = { styles.infoWithIcon } >
|
||||
<Icon
|
||||
name = 'timer'
|
||||
style = { styles.inlineIcon } />
|
||||
<Text style = { styles.confLength }>
|
||||
{ conferenceDurationString }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the server info component based on if the entry was on a
|
||||
* different server or not.
|
||||
*
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderServerInfo(recentListEntry) {
|
||||
if (recentListEntry.baseURL !== this.props._homeServer) {
|
||||
return (
|
||||
<View style = { styles.infoWithIcon } >
|
||||
<Icon
|
||||
name = 'public'
|
||||
style = { styles.inlineIcon } />
|
||||
<Text style = { styles.serverName }>
|
||||
{ recentListEntry.serverName }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the list of recently joined rooms.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} data - The row data to be rendered.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderRow(data) {
|
||||
|
@ -94,93 +180,6 @@ class RecentList extends AbstractRecentList {
|
|||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the style array of the avatar based on if the conference
|
||||
* was a home or remote server conference (based on current app setting).
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_getAvatarStyle(recentListEntry) {
|
||||
const avatarStyles = [ styles.avatar ];
|
||||
|
||||
if (recentListEntry.baseURL !== this.props._homeServer) {
|
||||
avatarStyles.push(
|
||||
this._getColorForServerName(recentListEntry.serverName)
|
||||
);
|
||||
}
|
||||
|
||||
return avatarStyles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a style (color) based on the server name, so then the
|
||||
* same server will always be rendered with the same avatar color.
|
||||
*
|
||||
* @private
|
||||
* @param {string} serverName - The recent list entry being rendered.
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getColorForServerName(serverName) {
|
||||
let nameHash = 0;
|
||||
|
||||
for (let i = 0; i < serverName.length; i++) {
|
||||
nameHash += serverName.codePointAt(i);
|
||||
}
|
||||
|
||||
return styles[`avatarRemoteServer${(nameHash % 5) + 1}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the server info component based on if the entry was
|
||||
* on a different server or not.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderServerInfo(recentListEntry) {
|
||||
if (recentListEntry.baseURL !== this.props._homeServer) {
|
||||
return (
|
||||
<View style = { styles.infoWithIcon } >
|
||||
<Icon
|
||||
name = 'public'
|
||||
style = { styles.inlineIcon } />
|
||||
<Text style = { styles.serverName }>
|
||||
{ recentListEntry.serverName }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the conference duration if available.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recentListEntry - The recent list entry being rendered.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderConfDuration(recentListEntry) {
|
||||
if (recentListEntry.conferenceDurationString) {
|
||||
return (
|
||||
<View style = { styles.infoWithIcon } >
|
||||
<Icon
|
||||
name = 'timer'
|
||||
style = { styles.inlineIcon } />
|
||||
<Text style = { styles.confLength }>
|
||||
{ recentListEntry.conferenceDurationString }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,8 +194,8 @@ class RecentList extends AbstractRecentList {
|
|||
function _mapStateToProps(state) {
|
||||
return {
|
||||
/**
|
||||
* The default server name based on which we determine
|
||||
* the render method.
|
||||
* The default server name based on which we determine the render
|
||||
* method.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
|
|
|
@ -1,57 +1,56 @@
|
|||
import {
|
||||
createStyleSheet,
|
||||
BoxModel
|
||||
} from '../../base/styles';
|
||||
import { createStyleSheet, BoxModel } from '../../base/styles';
|
||||
|
||||
const AVATAR_OPACITY = 0.4;
|
||||
|
||||
const AVATAR_SIZE = 65;
|
||||
|
||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||
|
||||
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature: recent list
|
||||
* The styles of the React {@code Component}s of the feature recent-list i.e.
|
||||
* {@code RecentList}.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
|
||||
/**
|
||||
* The style of the actual avatar
|
||||
* The style of the actual avatar.
|
||||
*/
|
||||
avatar: {
|
||||
width: AVATAR_SIZE,
|
||||
height: AVATAR_SIZE,
|
||||
alignItems: 'center',
|
||||
backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
|
||||
borderRadius: AVATAR_SIZE,
|
||||
height: AVATAR_SIZE,
|
||||
justifyContent: 'center',
|
||||
borderRadius: AVATAR_SIZE
|
||||
width: AVATAR_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the avatar container that makes the avatar rounded.
|
||||
*/
|
||||
avatarContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
paddingTop: 5
|
||||
},
|
||||
|
||||
/**
|
||||
* Simple {@code Text} content of the avatar (the actual initials)
|
||||
* Simple {@code Text} content of the avatar (the actual initials).
|
||||
*/
|
||||
avatarContent: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontSize: 32,
|
||||
fontWeight: '100',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* List of styles of the avatar of a remote meeting
|
||||
* (not the default server). The number of colors are limited
|
||||
* because they should match nicely.
|
||||
* List of styles of the avatar of a remote meeting (not the default
|
||||
* server). The number of colors are limited because they should match
|
||||
* nicely.
|
||||
*/
|
||||
avatarRemoteServer1: {
|
||||
backgroundColor: `rgba(232, 105, 156, ${AVATAR_OPACITY})`
|
||||
|
@ -74,7 +73,7 @@ export default createStyleSheet({
|
|||
},
|
||||
|
||||
/**
|
||||
* Style of the conference length (if rendered)
|
||||
* The style of the conference length (if rendered).
|
||||
*/
|
||||
confLength: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
|
@ -82,42 +81,41 @@ export default createStyleSheet({
|
|||
},
|
||||
|
||||
/**
|
||||
* This is the top level container style of the list
|
||||
* The top level container style of the list.
|
||||
*/
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Second line of the list (date).
|
||||
* May be extended with server name later.
|
||||
* Second line of the list (date). May be extended with server name later.
|
||||
*/
|
||||
date: {
|
||||
color: OVERLAY_FONT_COLOR
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the details container (right side) of the list
|
||||
* The style of the details container (right side) of the list.
|
||||
*/
|
||||
detailsContainer: {
|
||||
alignItems: 'flex-start',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 2 * BoxModel.margin,
|
||||
alignItems: 'flex-start'
|
||||
marginLeft: 2 * BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* The container for an info line with an inline icon.
|
||||
*/
|
||||
infoWithIcon: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center'
|
||||
justifyContent: 'flex-start'
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of an inline icon in an info line.
|
||||
* The style of an inline icon in an info line.
|
||||
*/
|
||||
inlineIcon: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
|
@ -125,27 +123,27 @@ export default createStyleSheet({
|
|||
},
|
||||
|
||||
/**
|
||||
* First line of the list (room name)
|
||||
* First line of the list (room name).
|
||||
*/
|
||||
roomName: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: OVERLAY_FONT_COLOR
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of one single row in the list
|
||||
* The style of one single row in the list.
|
||||
*/
|
||||
row: {
|
||||
padding: 8,
|
||||
paddingBottom: 0,
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
padding: 8,
|
||||
paddingBottom: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of the server name component (if rendered)
|
||||
* The style of the server name component (if rendered).
|
||||
*/
|
||||
serverName: {
|
||||
color: OVERLAY_FONT_COLOR,
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
/**
|
||||
* The max size of the list.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
export const LIST_SIZE = 30;
|
||||
|
||||
/**
|
||||
* The name of the {@code localStorage} item where recent rooms are stored.
|
||||
* The name of the {@code window.localStorage} item where recent rooms are
|
||||
* stored.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
|
|
|
@ -2,131 +2,116 @@
|
|||
|
||||
import moment from 'moment';
|
||||
|
||||
import { RECENT_URL_STORAGE } from './constants';
|
||||
|
||||
import { i18next } from '../base/i18n';
|
||||
import { parseURIString } from '../base/util';
|
||||
|
||||
import { RECENT_URL_STORAGE } from './constants';
|
||||
|
||||
/**
|
||||
* Retreives the recent room list and generates all the data needed
|
||||
* to be displayed.
|
||||
*
|
||||
* @returns {Promise} The {@code Promise} to be resolved when the list
|
||||
* is available.
|
||||
*/
|
||||
* Retreives the recent room list and generates all the data needed to be
|
||||
* displayed.
|
||||
*
|
||||
* @returns {Promise} The {@code Promise} to be resolved when the list is
|
||||
* available.
|
||||
*/
|
||||
export function getRecentRooms(): Promise<Array<Object>> {
|
||||
return new Promise(resolve => {
|
||||
window.localStorage._getItemAsync(RECENT_URL_STORAGE)
|
||||
.then(recentUrls => {
|
||||
if (recentUrls) {
|
||||
const recentUrlsObj = JSON.parse(recentUrls);
|
||||
return new Promise((resolve, reject) =>
|
||||
window.localStorage._getItemAsync(RECENT_URL_STORAGE).then(
|
||||
/* onFulfilled */ recentURLs => {
|
||||
const recentRoomDS = [];
|
||||
|
||||
for (const entry of recentUrlsObj) {
|
||||
const location = parseURIString(entry.conference);
|
||||
if (recentURLs) {
|
||||
for (const e of JSON.parse(recentURLs)) {
|
||||
const location = parseURIString(e.conference);
|
||||
|
||||
if (location && location.room && location.hostname) {
|
||||
recentRoomDS.push({
|
||||
baseURL:
|
||||
`${location.protocol}//${location.host}`,
|
||||
conference: entry.conference,
|
||||
dateTimeStamp: entry.date,
|
||||
conferenceDuration: entry.conferenceDuration,
|
||||
dateString: _getDateString(
|
||||
entry.date
|
||||
),
|
||||
conferenceDurationString: _getLengthString(
|
||||
entry.conferenceDuration
|
||||
),
|
||||
conference: e.conference,
|
||||
conferenceDuration: e.conferenceDuration,
|
||||
conferenceDurationString:
|
||||
_getDurationString(e.conferenceDuration),
|
||||
dateString: _getDateString(e.date),
|
||||
dateTimeStamp: e.date,
|
||||
initials: _getInitials(location.room),
|
||||
room: location.room,
|
||||
serverName: location.hostname
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(recentRoomDS.reverse());
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives the recent URL list as a list of objects.
|
||||
*
|
||||
* @returns {Array} The list of already stored recent URLs.
|
||||
*/
|
||||
export function getRecentUrls() {
|
||||
let recentUrls = window.localStorage.getItem(RECENT_URL_STORAGE);
|
||||
|
||||
if (recentUrls) {
|
||||
recentUrls = JSON.parse(recentUrls);
|
||||
} else {
|
||||
recentUrls = [];
|
||||
}
|
||||
|
||||
return recentUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the recent URL list.
|
||||
*
|
||||
* @param {Array} recentUrls - The new URL list.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function updaterecentUrls(recentUrls: Array<Object>) {
|
||||
window.localStorage.setItem(
|
||||
RECENT_URL_STORAGE,
|
||||
JSON.stringify(recentUrls)
|
||||
},
|
||||
/* onRejected */ reject)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a well formatted date string to be displayed in the list.
|
||||
*
|
||||
* @private
|
||||
* @param {number} dateTimeStamp - The UTC timestamp to be converted to String.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getDateString(dateTimeStamp: number) {
|
||||
const date = new Date(dateTimeStamp);
|
||||
* Retreives the recent URL list as a list of objects.
|
||||
*
|
||||
* @returns {Array} The list of already stored recent URLs.
|
||||
*/
|
||||
export function getRecentURLs() {
|
||||
const recentURLs = window.localStorage.getItem(RECENT_URL_STORAGE);
|
||||
|
||||
if (date.toDateString() === new Date().toDateString()) {
|
||||
// the date is today, we use fromNow format
|
||||
|
||||
return moment(date)
|
||||
.locale(i18next.language)
|
||||
.fromNow();
|
||||
}
|
||||
|
||||
return moment(date)
|
||||
.locale(i18next.language)
|
||||
.format('lll');
|
||||
return recentURLs ? JSON.parse(recentURLs) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a well formatted duration string to be displayed
|
||||
* as the conference length.
|
||||
*
|
||||
* @private
|
||||
* @param {number} duration - The duration in MS.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getLengthString(duration: number) {
|
||||
* Updates the recent URL list.
|
||||
*
|
||||
* @param {Array} recentURLs - The new URL list.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function updateRecentURLs(recentURLs: Array<Object>) {
|
||||
window.localStorage.setItem(
|
||||
RECENT_URL_STORAGE,
|
||||
JSON.stringify(recentURLs)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a well formatted date string to be displayed in the list.
|
||||
*
|
||||
* @param {number} dateTimeStamp - The UTC timestamp to be converted to String.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getDateString(dateTimeStamp: number) {
|
||||
const date = new Date(dateTimeStamp);
|
||||
const m = moment(date).locale(i18next.language);
|
||||
|
||||
if (date.toDateString() === new Date().toDateString()) {
|
||||
// The date is today, we use fromNow format.
|
||||
return m.fromNow();
|
||||
}
|
||||
|
||||
return m.format('lll');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a well formatted duration string to be displayed as the conference
|
||||
* length.
|
||||
*
|
||||
* @param {number} duration - The duration in MS.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getDurationString(duration: number) {
|
||||
return moment.duration(duration)
|
||||
.locale(i18next.language)
|
||||
.humanize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initials supposed to be used based on the room name.
|
||||
*
|
||||
* @private
|
||||
* @param {string} room - The room name.
|
||||
* @returns {string}
|
||||
*/
|
||||
* Returns the initials supposed to be used based on the room name.
|
||||
*
|
||||
* @param {string} room - The room name.
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getInitials(room: string) {
|
||||
return room && room.charAt(0) ? room.charAt(0).toUpperCase() : '?';
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/* @flow */
|
||||
|
||||
import { LIST_SIZE } from './constants';
|
||||
import { getRecentUrls, updaterecentUrls } from './functions';
|
||||
// @flow
|
||||
|
||||
import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { LIST_SIZE } from './constants';
|
||||
import { getRecentURLs, updateRecentURLs } from './functions';
|
||||
|
||||
/**
|
||||
* Middleware that captures joined rooms so then it can be saved to
|
||||
* {@code localStorage}
|
||||
* Middleware that captures joined rooms so they can be saved into
|
||||
* {@code window.localStorage}.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
|
@ -26,44 +26,44 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
});
|
||||
|
||||
/**
|
||||
* Stores the recently joined room in {@code localStorage}.
|
||||
* Stores the recently joined room into {@code window.localStorage}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action CONFERENCE_JOINED which is being
|
||||
* @param {Action} action - The redux action {@code SET_ROOM} which is being
|
||||
* dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified action.
|
||||
*/
|
||||
function _storeJoinedRoom(store, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const { room } = action;
|
||||
|
||||
if (room) {
|
||||
const { locationURL } = store.getState()['features/base/connection'];
|
||||
const conferenceLink = locationURL.href;
|
||||
const conference = locationURL.href;
|
||||
|
||||
// if the current conference is already in the list,
|
||||
// we remove it to add it
|
||||
// to the top at the end
|
||||
const recentUrls = getRecentUrls().filter(
|
||||
entry => entry.conference !== conferenceLink
|
||||
);
|
||||
// If the current conference is already in the list, we remove it to add
|
||||
// it to the top at the end.
|
||||
const recentURLs
|
||||
= getRecentURLs()
|
||||
.filter(e => e.conference !== conference);
|
||||
|
||||
// please note, this is a reverse sorted array
|
||||
// (newer elements at the end)
|
||||
recentUrls.push({
|
||||
conference: conferenceLink,
|
||||
date: Date.now(),
|
||||
conferenceDuration: 0
|
||||
// XXX This is a reverse sorted array (i.e. newer elements at the end).
|
||||
recentURLs.push({
|
||||
conference,
|
||||
conferenceDuration: 0,
|
||||
date: Date.now()
|
||||
});
|
||||
|
||||
// maximising the size
|
||||
recentUrls.splice(0, recentUrls.length - LIST_SIZE);
|
||||
recentURLs.splice(0, recentURLs.length - LIST_SIZE);
|
||||
|
||||
updaterecentUrls(recentUrls);
|
||||
updateRecentURLs(recentURLs);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -72,33 +72,35 @@ function _storeJoinedRoom(store, next, action) {
|
|||
/**
|
||||
* Updates the conference length when left.
|
||||
*
|
||||
* @private
|
||||
* @param {Store} store - The redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action CONFERENCE_JOINED which is being
|
||||
* dispatched in the specified store.
|
||||
* @param {Action} action - The redux action {@code CONFERENCE_WILL_LEAVE} which
|
||||
* is being dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified action.
|
||||
*/
|
||||
function _updateConferenceDuration(store, next, action) {
|
||||
function _updateConferenceDuration({ getState }, next, action) {
|
||||
const result = next(action);
|
||||
const { locationURL } = store.getState()['features/base/connection'];
|
||||
|
||||
const { locationURL } = getState()['features/base/connection'];
|
||||
|
||||
if (locationURL && locationURL.href) {
|
||||
const recentUrls = getRecentUrls();
|
||||
const recentURLs = getRecentURLs();
|
||||
|
||||
if (recentUrls.length > 0
|
||||
&& recentUrls[recentUrls.length - 1].conference
|
||||
=== locationURL.href) {
|
||||
// the last conference start was stored
|
||||
// so we need to update the length
|
||||
if (recentURLs.length > 0) {
|
||||
const mostRecentURL = recentURLs[recentURLs.length - 1];
|
||||
|
||||
recentUrls[recentUrls.length - 1].conferenceDuration
|
||||
= Date.now() - recentUrls[recentUrls.length - 1].date;
|
||||
if (mostRecentURL.conference === locationURL.href) {
|
||||
// The last conference start was stored so we need to update the
|
||||
// length.
|
||||
mostRecentURL.conferenceDuration
|
||||
= Date.now() - mostRecentURL.date;
|
||||
|
||||
updaterecentUrls(recentUrls);
|
||||
updateRecentURLs(recentURLs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,6 @@ import React from 'react';
|
|||
import { TextInput, TouchableHighlight, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
||||
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { Link, LoadingIndicator, Text } from '../../base/react';
|
||||
|
@ -13,6 +9,10 @@ import { ColorPalette } from '../../base/styles';
|
|||
import { createDesiredLocalTracks } from '../../base/tracks';
|
||||
import { RecentList } from '../../recent-list';
|
||||
|
||||
import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage';
|
||||
import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
|
||||
import styles, { PLACEHOLDER_TEXT_COLOR } from './styles';
|
||||
|
||||
/**
|
||||
* The URL at which the privacy policy is available to the user.
|
||||
*/
|
||||
|
|
|
@ -5,13 +5,13 @@ import {
|
|||
fixAndroidViewClipping
|
||||
} from '../../base/styles';
|
||||
|
||||
export const PLACEHOLDER_TEXT_COLOR = 'rgba(255, 255, 255, 0.3)';
|
||||
|
||||
/**
|
||||
* The default color of text on the WelcomePage.
|
||||
*/
|
||||
const TEXT_COLOR = ColorPalette.white;
|
||||
|
||||
export const PLACEHOLDER_TEXT_COLOR = 'rgba(255, 255, 255, 0.3)';
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature welcome including
|
||||
* {@code WelcomePage} and {@code BlankPage}.
|
||||
|
|
Loading…
Reference in New Issue