Live location sharing: beacon list view tiles (#8363)

* add basic sidebar container

Signed-off-by: Kerry Archibald <kerrya@element.io>

* optionally show icon in beaconstatus

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add avatar and style list item

Signed-off-by: Kerry Archibald <kerrya@element.io>

* formatted last update time

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test beacon list item

Signed-off-by: Kerry Archibald <kerrya@element.io>

* move makeRoomWithState events to test utils

Signed-off-by: Kerry Archibald <kerrya@element.io>

* move beacon test helpers into utils

Signed-off-by: Kerry Archibald <kerrya@element.io>

* newline

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add copyable text to beacon list item

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add copyable geo uri to list item

Signed-off-by: Kerry Archibald <kerrya@element.io>

* improve spacing

Signed-off-by: Kerry Archibald <kerrya@element.io>

* overflow scroll on list

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry 2022-04-20 13:57:50 +02:00 committed by GitHub
parent 2f6b76755c
commit 4a38cbd550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 355 additions and 10 deletions

View file

@ -0,0 +1,82 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useContext } from 'react';
import { Beacon, BeaconEvent } from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
import { humanizeTime } from '../../../utils/humanize';
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import CopyableText from '../elements/CopyableText';
import BeaconStatus from './BeaconStatus';
import { BeaconDisplayStatus } from './displayStatus';
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
interface Props {
beacon: Beacon;
}
const BeaconListItem: React.FC<Props> = ({ beacon }) => {
const latestLocationState = useEventEmitterState(
beacon,
BeaconEvent.LocationUpdate,
() => beacon.latestLocationState,
);
const matrixClient = useContext(MatrixClientContext);
const room = matrixClient.getRoom(beacon.roomId);
if (!latestLocationState || !beacon.isLive) {
return null;
}
const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
const beaconMember = isSelfLocation ?
room.getMember(beacon.beaconInfoOwner) :
undefined;
const humanizedUpdateTime = humanizeTime(latestLocationState.timestamp);
return <li className='mx_BeaconListItem'>
{ isSelfLocation ?
<MemberAvatar
className='mx_BeaconListItem_avatar'
member={beaconMember}
height={32}
width={32}
/> :
<StyledLiveBeaconIcon className='mx_BeaconListItem_avatarIcon' />
}
<div className='mx_BeaconListItem_info'>
<BeaconStatus
className='mx_BeaconListItem_status'
beacon={beacon}
label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner}
displayStatus={BeaconDisplayStatus.Active}
>
<CopyableText
border={false}
getTextToCopy={() => latestLocationState?.uri}
/>
</BeaconStatus>
<span className='mx_BeaconListItem_lastUpdated'>{ _t("Updated %(humanizedUpdateTime)s", { humanizedUpdateTime }) }</span>
</div>
</li>;
};
export default BeaconListItem;

View file

@ -28,6 +28,7 @@ import { formatTime } from '../../../DateUtils';
interface Props {
displayStatus: BeaconDisplayStatus;
displayLiveTimeRemaining?: boolean;
withIcon?: boolean;
beacon?: Beacon;
label?: string;
}
@ -45,6 +46,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
label,
className,
children,
withIcon,
...rest
}) => {
const isIdle = displayStatus === BeaconDisplayStatus.Loading ||
@ -54,11 +56,11 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
{...rest}
className={classNames('mx_BeaconStatus', `mx_BeaconStatus_${displayStatus}`, className)}
>
<StyledLiveBeaconIcon
{ withIcon && <StyledLiveBeaconIcon
className='mx_BeaconStatus_icon'
withError={displayStatus === BeaconDisplayStatus.Error}
isIdle={isIdle}
/>
/> }
<div className='mx_BeaconStatus_description'>
{ displayStatus === BeaconDisplayStatus.Loading && <span>{ _t('Loading live location...') }</span> }
@ -68,7 +70,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
{ displayStatus === BeaconDisplayStatus.Active && beacon && <>
<>
{ label }
<span className='mx_BeaconStatus_label'>{ label }</span>
{ displayLiveTimeRemaining ?
<LiveTimeRemaining beacon={beacon} /> :
<BeaconExpiryTime beacon={beacon} />

View file

@ -21,6 +21,7 @@ import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import Heading from '../typography/Heading';
import BeaconListItem from './BeaconListItem';
interface Props {
beacons: Beacon[];
@ -41,8 +42,7 @@ const DialogSidebar: React.FC<Props> = ({ beacons, requestClose }) => {
</AccessibleButton>
</div>
<ol className='mx_DialogSidebar_list'>
{ /* TODO nice elements */ }
{ beacons.map((beacon, index) => <li key={beacon.identifier}>{ index }</li>) }
{ beacons.map((beacon) => <BeaconListItem key={beacon.identifier} beacon={beacon} />) }
</ol>
</div>;
};

View file

@ -54,6 +54,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
displayStatus={ownDisplayStatus}
label={_t('Live location enabled')}
displayLiveTimeRemaining
withIcon
{...rest}
>
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton

View file

@ -24,7 +24,7 @@ import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
interface IProps {
children: React.ReactNode;
children?: React.ReactNode;
getTextToCopy: () => string;
border?: boolean;
}

View file

@ -152,6 +152,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
beacon={beacon}
displayStatus={displayStatus}
label={_t('View live location')}
withIcon
/>
}
</div>